├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── build.py ├── docker-compose.yml ├── mmp-kompakt ├── 2019-01.pdf └── 2019-02.pdf ├── out ├── ridesharing-api-dev-de.docx ├── ridesharing-api-dev-de.epub ├── ridesharing-api-dev-de.html ├── ridesharing-api-dev-de.odt ├── ridesharing-api-dev-de.pdf └── ridesharing-api-dev-de.txt ├── resources ├── html5.css ├── live.html ├── lizenz-als-bild.md ├── lizenz-als-text.md ├── template.tex ├── titelbild.png ├── uml.svg └── uml.xmi ├── schema ├── Calendar.json ├── CalendarException.json ├── Car.json ├── Location.json ├── Participation.json ├── Person.json ├── PersonContact.json ├── Preferences.json ├── Route.json ├── SingleLocation.json ├── SingleStop.json ├── SingleTrip.json ├── Stop.json ├── System.json ├── Trip.json └── strings.yml ├── scripts ├── __init__.py ├── __init__.pyc ├── extract_urls.py ├── json_schema2markdown.py ├── json_schema2markdown.pyc ├── test.sh └── validate.py ├── setup.tex └── src ├── 0-00-00-header.md ├── 1-00-00-einleitung.md ├── 1-01-00-zielsetzung.md ├── 1-02-00-nutzungsszenarien.md ├── 1-03-00-marktumfeld.md ├── 1-03-01-metaportal.md ├── 1-03-02-multimodalitaet.md ├── 1-03-02-profildaten-export.md ├── 1-04-00-bestehende-schnittstellen.md ├── 1-05-00-nomenklatur.md ├── 1-06-00-datenschutz.md ├── 1-07-00-autoren.md ├── 2-00-prinzipien-und-funktionen-der-schnittstelle.md ├── 2-01-00-designprinzipien.md ├── 2-02-zukunftssicherheit.md ├── 2-03-urls.md ├── 2-04-json-ausgabe.md ├── 2-05-objektlisten-und-paginierung.md ├── 2-06-cross-origin-resource-sharing-cors.md ├── 2-08-geloeschte-objekte.md ├── 2-09-ausnahmebehandlung.md ├── 2-10-endpunkt.md ├── 2-11-instanzierung.md ├── 3-00-schema.md ├── 3-01-eigenschaften-mit-verwendung-in-mehreren-objekttypen.md ├── 4-00-sucherweiterung.md └── images ├── CC-BY-SA.svg ├── objekttypen_graph.dot ├── uml.svg └── url.svg /.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/.dockerignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | ._* 4 | .cache 5 | __pycache__ 6 | scripts/__pycache__ 7 | 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | LABEL maintainer "Ernesto Ruge " 3 | ENV PYTHONUNBUFFERED 1 4 | ENV DEBIAN_FRONTEND noninteractive 5 | ENV LANG en_US.utf8 6 | ENV LC_ALL en_US.utf8 7 | ENV LANGUAGE en_US.utf8 8 | 9 | RUN apt-get update && \ 10 | apt-get install -y locales apt-utils && \ 11 | locale-gen en_US en_US.UTF-8 && \ 12 | echo -e 'LANG="en_US.UTF-8"\nLANGUAGE="en_US:en"\n' > /etc/default/locale && \ 13 | apt-get dist-upgrade -y && \ 14 | apt-get autoremove -y && \ 15 | apt-get clean 16 | RUN apt-get update && apt-get install -y bzip2 ghostscript git-core graphviz lmodern pandoc pandoc-citeproc qpdf tar \ 17 | zip python3 python3-pip python3-setuptools imagemagick \ 18 | texlive texlive-lang-german texlive-latex-recommended texlive-generic-recommended texlive-fonts-recommended \ 19 | texlive-latex-extra texlive-luatex texlive-xetex 20 | RUN pip3 install pyyaml 21 | 22 | RUN groupadd -g 1002 webdev 23 | RUN useradd -u 1002 -g webdev -m -d /home/webdev -s /bin/bash webdev 24 | 25 | ENV HOME /home/webdev 26 | RUN mkdir /app 27 | WORKDIR /app 28 | COPY --chown=webdev:webdev . /app 29 | USER webdev -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ridesharing-API: Dokumentation 2 | 3 | Dies ist das Dokumentations-Repository einer Schnittstelle für Ridesharing. Es soll betrachtet werden, welche Ridesharing-APIs es gibt und wie diese sich in einer einheitlichen Schnittstelle zusammenfassen lassen. Am Ende soll eine Schnittstelle entstehen, die als Austauschformat für ein zentrales Meta-Portal sowie für alle Formen dezentraler Austauschformen genutzt werden kann. 4 | 5 | Die Entwicklung findet offen und transparent als Community-Projekt der [Open Knowledge Foundation](https://okfn.de) statt. Vorbild für die Entwicklung ist [OParl](https://oparl.org). 6 | 7 | Initiiert wurde die Entwicklung vom [MetaMitfahrPort](https://mmport.de), welches vom [BMVI](https://www.bmvi.de) via [mFund](https://www.bmvi.de/DE/Themen/Digitales/mFund/Ueberblick/ueberblick.html) gefördert wird. 8 | 9 | # Ansehen 10 | 11 | Die aktuelle Development-Version befindet sich [im Ordner /out](https://github.com/ridesharing-api/documentation/tree/master/out). Die aktuelle PDF-Ausgabe kann [hier heruntergeladen werden](https://github.com/ridesharing-api/documentation/raw/master/out/ridesharing-api-dev-de.pdf). 12 | 13 | # Mitmachen 14 | 15 | Verbesserungen, Diskussionen und weitere Informationen wie weitere Schnittstellen oder Daten sind herzlich willkommen. 16 | 17 | Als Plattform für die Diskussion eignet sich v.a. der [Bereich Issues hier auf Github](https://github.com/ridesharing-api/documentation/issues), da so eine für alle transparente Diskussion entstehen kann. 18 | 19 | Außerdem steht [eine Mailingliste zur Verfügung](https://mlists.okfn.de/cgi-bin/mailman/listinfo/ridesharing). 20 | 21 | Wenn ein persönlicher Kontakt z.B. für eine Freigabe einer privaten Schnittstelle gewüscht ist: bitte Mail an [Ernesto Ruge, mail@ernestoruge.de](mailto:mail@ernestoruge.de). 22 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This program is part of MetaMitfahrPort and may be used to build the specification 3 | # It's heavily inspired by OParl and its build system: https://github.com/OParl/spec 4 | 5 | import os 6 | import shlex 7 | import shutil 8 | import subprocess 9 | import sys 10 | from argparse import ArgumentParser 11 | from glob import glob 12 | from os import path 13 | 14 | from scripts.json_schema2markdown import schema_to_markdown 15 | 16 | SPECIFICATION_BUILD_ACTIONS = [ 17 | 'all', 18 | 'clean', 19 | 'test', 20 | 'live', 21 | 'html', 22 | 'pdf', 23 | 'odt', 24 | 'docx', 25 | 'txt', 26 | 'epub', 27 | 'archives', 28 | 'zip', 29 | 'gz', 30 | 'bz' 31 | ] 32 | 33 | SPECIFICATION_BUILD_TOOLS = [ 34 | 'pandoc', 35 | 'dot', 36 | 'xelatex', 37 | 'gs', 38 | 'convert', 39 | 'python3', 40 | 'tar', 41 | 'zip' 42 | ] 43 | 44 | SPECIFICATION_BUILD_FLAGS = { 45 | 'gs': '-dQUIET -dSAFER -dBATCH -dNOPAUSE -sDisplayHandle=0 -sDEVICE=png16m -r600 -dTextAlphaBits=4', 46 | 'pandoc': '--from markdown --standalone --table-of-contents --toc-depth=2 --number-sections' 47 | } 48 | 49 | 50 | def configure_argument_parser(): 51 | parser = ArgumentParser( 52 | prog='./build.py', 53 | epilog=''' 54 | build.py is part of the OParl Specification and thus distributed 55 | under the terms of the Creative Commons SA 4.0 License. 56 | ''' 57 | ) 58 | 59 | parser.add_argument( 60 | '--language', 61 | '-l', 62 | help='Specification language', 63 | default='de', 64 | action='store', 65 | ) 66 | 67 | parser.add_argument( 68 | '--version', 69 | '-V', 70 | help='This will be displayed as version in the specification. Defaults to `git desribe`', 71 | action='store', 72 | ) 73 | 74 | parser.add_argument( 75 | '--print-basename', 76 | help='This will output the base name used for build output', 77 | action='store_true', 78 | dest='print_basename' 79 | ) 80 | 81 | parser.add_argument( 82 | '--latex-template', 83 | help='Change the latex template used for PDF generation', 84 | action='store', 85 | default='resources/template.tex' 86 | ) 87 | 88 | parser.add_argument( 89 | '--html-style', 90 | help='Change the CSS file used for styling the HTML output', 91 | action='store', 92 | default='resources/html5.css' 93 | ) 94 | 95 | parser.add_argument( 96 | '--list-actions', 97 | help='List available build actions', 98 | dest='list_actions', 99 | action='store_true' 100 | ) 101 | 102 | parser.add_argument( 103 | 'action', 104 | help='Build action to take, available actions are: {}'.format(', '.join(SPECIFICATION_BUILD_ACTIONS)), 105 | action='store', 106 | nargs='*' 107 | ) 108 | 109 | return parser 110 | 111 | 112 | def check_build_action(action): 113 | if len(action) == 0: 114 | return 'all' 115 | 116 | if action[0] in SPECIFICATION_BUILD_ACTIONS: 117 | return action[0] 118 | 119 | raise Exception( 120 | 'Unknown build action: {}, choose one of: {}'.format(action, ', '.join(SPECIFICATION_BUILD_ACTIONS))) 121 | 122 | 123 | def get_git_describe_version(): 124 | return 'dev' 125 | #return subprocess.check_output('git describe', shell=True, universal_newlines=True).strip() 126 | 127 | 128 | def check_available_tools(): 129 | tools = {} 130 | for tool in SPECIFICATION_BUILD_TOOLS: 131 | executable = shutil.which(tool) 132 | if executable: 133 | tools[tool] = executable 134 | else: 135 | raise Exception('{} not found, aborting.'.format(tool)) 136 | 137 | return tools 138 | 139 | 140 | def get_filename_base(language, version): 141 | return 'ridesharing-api-{}-{}'.format(version, language) 142 | 143 | 144 | def prepare_builddir(filename_base): 145 | os.makedirs('build/src/images') 146 | os.makedirs('build/{}'.format(filename_base)) 147 | 148 | 149 | def prepare_schema(language): 150 | language_file = 'schema/strings.yml' 151 | if language != 'de': 152 | language_file = 'locales/{}/schema/strings.yml'.format(language) 153 | 154 | output_file = 'build/src/3-99-schema.md' 155 | 156 | schema_to_markdown('schema', 'examples', output_file, language, language_file) 157 | 158 | 159 | def prepare_markdown(language): 160 | glob_pattern = 'src/*.md' 161 | if language != 'de': 162 | glob_pattern = 'locales/{}/src/*.md'.format(language) 163 | 164 | files = glob(glob_pattern) 165 | for f in files: 166 | shutil.copy2(f, 'build/src/') 167 | 168 | os.mkdir('build/extra') 169 | with open('build/extra/detailed-version.md', 'w') as fp: 170 | fp.write("Version {}\n".format(get_git_describe_version())) 171 | 172 | 173 | def prepare_images(tools): 174 | glob_pattern = 'src/images/*.*' 175 | 176 | files = glob(glob_pattern) 177 | for f in files: 178 | convert_command = '' 179 | filename, extension = os.path.splitext(f) 180 | fout = path.join('build', 'src', 'images', os.path.basename(filename) + '.png') 181 | 182 | shutil.copy2(f, fout) 183 | 184 | if extension == '.pdf': 185 | convert_command = '{} {} -sOutputFile={} -f {}'.format( 186 | tools['gs'], 187 | SPECIFICATION_BUILD_FLAGS['gs'], 188 | fout, 189 | f 190 | ) 191 | 192 | if extension == '.dot': 193 | convert_command = '{} -Tpng {} -o {}'.format( 194 | tools['dot'], 195 | f, 196 | fout 197 | ) 198 | 199 | if extension == '.svg': 200 | convert_command = '{} {} {}'.format( 201 | tools['convert'], 202 | f, 203 | fout 204 | ) 205 | 206 | cmd = shlex.split(convert_command) 207 | try: 208 | subprocess.run(cmd, check=True) 209 | except subprocess.CalledProcessError: 210 | raise Exception( 211 | 'Errored on image prep for {}, please check the command:\n{}'.format( 212 | f, convert_command 213 | ) 214 | ) 215 | 216 | 217 | def create_symlinks(filename_base): 218 | """ Allow having stable links when working on the spec """ 219 | os.makedirs('build/latest') 220 | for specfile in os.listdir('build/{}'.format(filename_base)): 221 | existing = os.path.abspath('build/{}/{}'.format(filename_base, specfile)) 222 | new = 'build/latest/ridesharing-api{}'.format(os.path.splitext(specfile)[-1]) 223 | os.symlink(existing, new) 224 | 225 | 226 | def get_pandoc_version(pandoc_bin): 227 | version_tuple = subprocess.getoutput('{} --version'.format(pandoc_bin)).split('\n')[0].split(' ')[1].split('.') 228 | return [int(v) for v in version_tuple] 229 | 230 | 231 | def run_pandoc(pandoc_bin, filename_base, output_format, extra_args='', extra_files=''): 232 | output_file = 'build/{}/{}.{}'.format(filename_base, filename_base, output_format) 233 | if path.exists(output_file): 234 | return 235 | 236 | source_files = glob('build/src/*.md') 237 | source_files.sort(key=lambda file: path.basename(file)) 238 | # This gets the version into the titlepage of the pdf 239 | detailed_version = '-M detailed-version={}'.format(get_git_describe_version()) 240 | 241 | # NOTE: Once we can safely assume pandoc 2.0, we can use resource-path 242 | # for neater include management in the markdown files 243 | # pandoc_command = '{} {} {} --resource-path=.:build/src -o {} {} {}'.format( 244 | pandoc_command = '{} {} {} -o {} {} {} {}'.format( 245 | pandoc_bin, 246 | SPECIFICATION_BUILD_FLAGS['pandoc'], 247 | extra_args, 248 | output_file, 249 | extra_files, 250 | detailed_version, 251 | ' '.join(source_files) 252 | ) 253 | 254 | cmd = shlex.split(pandoc_command) 255 | try: 256 | subprocess.run(cmd, check=True, stdout=sys.stdout, stderr=sys.stderr) 257 | except subprocess.CalledProcessError: 258 | raise Exception( 259 | 'Errored on pandoc: {}'.format(pandoc_command) 260 | ) 261 | 262 | 263 | class Action: 264 | @staticmethod 265 | def clean(): 266 | shutil.rmtree('build', ignore_errors=True) 267 | 268 | @staticmethod 269 | def test(): 270 | # TODO: validate.py appears to be broken 271 | pass 272 | 273 | @staticmethod 274 | def live(tools, _, filename_base): 275 | args = '--to html5 --section-divs --no-highlight --template=resources/live.html' 276 | run_pandoc(tools['pandoc'], filename_base, 'html', extra_args=args) 277 | 278 | @staticmethod 279 | def html(tools, options, filename_base): 280 | args = '--to html5 --css {} --section-divs --self-contained'.format(options.html_style) 281 | run_pandoc(tools['pandoc'], filename_base, 'html', extra_args=args, 282 | extra_files='build/extra/detailed-version.md resources/lizenz-als-bild.md') 283 | 284 | @staticmethod 285 | def pdf(tools, options, filename_base): 286 | args = '--pdf-engine=xelatex' 287 | if get_pandoc_version(tools['pandoc'])[0] < 2: 288 | args = '--latex-engine=xelatex' 289 | 290 | args += ' --template {}'.format(options.latex_template) 291 | 292 | run_pandoc(tools['pandoc'], filename_base, 'pdf', extra_args=args) 293 | 294 | @staticmethod 295 | def odt(tools, _, filename_base): 296 | run_pandoc(tools['pandoc'], filename_base, 'odt', 297 | extra_files='build/extra/detailed-version.md resources/lizenz-als-text.md') 298 | 299 | @staticmethod 300 | def docx(tools, _, filename_base): 301 | run_pandoc(tools['pandoc'], filename_base, 'docx', 302 | extra_files='build/extra/detailed-version.md resources/lizenz-als-text.md') 303 | 304 | @staticmethod 305 | def txt(tools, _, filename_base): 306 | run_pandoc(tools['pandoc'], filename_base, 'txt') 307 | 308 | @staticmethod 309 | def epub(tools, _, filename_base): 310 | run_pandoc(tools['pandoc'], filename_base, 'epub', extra_files='build/extra/detailed-version.md') 311 | 312 | @staticmethod 313 | def all(tools, options, filename_base): 314 | Action.html(tools, options, filename_base) 315 | Action.pdf(tools, options, filename_base) 316 | Action.odt(tools, options, filename_base) 317 | Action.docx(tools, options, filename_base) 318 | Action.txt(tools, options, filename_base) 319 | Action.epub(tools, options, filename_base) 320 | 321 | @staticmethod 322 | def zip(tools, options, filename_base): 323 | Action.all(tools, options, filename_base) 324 | archive_name = '{}.zip'.format(filename_base) 325 | subprocess.run(['zip', '-qr', archive_name, filename_base], cwd='build') 326 | 327 | @staticmethod 328 | def gz(tools, options, filename_base): 329 | Action.all(tools, options, filename_base) 330 | archive_name = '{}.tar.gz'.format(filename_base) 331 | subprocess.run(['tar', '-czf', archive_name, filename_base], cwd='build') 332 | 333 | @staticmethod 334 | def bz(tools, options, filename_base): 335 | Action.all(tools, options, filename_base) 336 | archive_name = '{}.tar.bz2'.format(filename_base) 337 | subprocess.run(['tar', '-cjf', archive_name, filename_base], cwd='build') 338 | 339 | @staticmethod 340 | def archives(tools, options, filename_base): 341 | Action.zip(tools, options, filename_base) 342 | Action.gz(tools, options, filename_base) 343 | Action.bz(tools, options, filename_base) 344 | 345 | 346 | def main(): 347 | options = configure_argument_parser().parse_args() 348 | action = check_build_action(options.action) 349 | 350 | if options.version is None: 351 | options.version = get_git_describe_version() 352 | 353 | filename_base = get_filename_base(options.language, options.version) 354 | 355 | if options.print_basename: 356 | print(filename_base) 357 | exit(0) 358 | 359 | if options.list_actions: 360 | for action in SPECIFICATION_BUILD_ACTIONS: 361 | print('- ' + action) 362 | exit() 363 | 364 | tools = check_available_tools() 365 | 366 | # always clean 367 | Action.clean() 368 | 369 | if action == 'clean': 370 | exit(0) 371 | 372 | prepare_builddir(filename_base) 373 | prepare_schema(options.language) 374 | prepare_markdown(options.language) 375 | prepare_images(tools) 376 | 377 | # Avoid much boilerplate 378 | getattr(Action, action)(tools, options, filename_base) 379 | 380 | create_symlinks(filename_base) 381 | 382 | 383 | if __name__ == '__main__': 384 | main() 385 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | docs: 4 | container_name: ridesharing-docs 5 | restart: always 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | volumes: 10 | - .:/app 11 | entrypoint: ['sleep', '60000'] -------------------------------------------------------------------------------- /mmp-kompakt/2019-01.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/mmp-kompakt/2019-01.pdf -------------------------------------------------------------------------------- /mmp-kompakt/2019-02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/mmp-kompakt/2019-02.pdf -------------------------------------------------------------------------------- /out/ridesharing-api-dev-de.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/out/ridesharing-api-dev-de.docx -------------------------------------------------------------------------------- /out/ridesharing-api-dev-de.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/out/ridesharing-api-dev-de.epub -------------------------------------------------------------------------------- /out/ridesharing-api-dev-de.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/out/ridesharing-api-dev-de.odt -------------------------------------------------------------------------------- /out/ridesharing-api-dev-de.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/out/ridesharing-api-dev-de.pdf -------------------------------------------------------------------------------- /resources/html5.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | font-size: 14px; 4 | line-height: 1.5; 5 | color: #222; 6 | } 7 | 8 | p { 9 | margin: 0.5em 0; 10 | margin-top: 0; 11 | margin-bottom: 14px; 12 | } 13 | 14 | img { 15 | max-width: 100%; 16 | } 17 | 18 | table { 19 | border-collapse: collapse; 20 | margin-bottom: 1.5em; 21 | } 22 | 23 | .sourceCode pre { 24 | margin: 0; 25 | } 26 | 27 | table, 28 | th, 29 | td { 30 | font-size:14px; 31 | border: 1px solid #222; 32 | padding: 5px; 33 | } 34 | 35 | #TOC { 36 | position: fixed; 37 | width: 280px; 38 | padding: 8px; 39 | margin: 0; 40 | top: 0; 41 | left: 0; 42 | bottom: 0; 43 | overflow-y: auto; 44 | background-color: #EEEEEE; 45 | } 46 | 47 | #TOC a { 48 | text-decoration: none; 49 | color: black; 50 | } 51 | 52 | #TOC a:hover { 53 | text-decoration: underline; 54 | } 55 | 56 | #TOC ul { 57 | list-style-type: none; 58 | padding-left: 0px; 59 | line-height: 18px; 60 | } 61 | 62 | 63 | #TOC > ul > li { 64 | margin: 15px 0; 65 | font-size: 16px; 66 | font-weight: bold; 67 | } 68 | 69 | #TOC > ul > li > ul { 70 | margin-top: 5px; 71 | font-size: 14px; 72 | font-weight: normal; 73 | } 74 | 75 | #TOC > ul> li > ul > li > ul { 76 | padding-left: 15px; 77 | } 78 | 79 | body > section, 80 | body > p, 81 | body > header { 82 | margin-left: 310px; 83 | max-width: 800px; 84 | } -------------------------------------------------------------------------------- /resources/live.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 |
8 | $body$ 9 |
10 | 11 | -------------------------------------------------------------------------------- /resources/lizenz-als-bild.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/resources/lizenz-als-bild.md -------------------------------------------------------------------------------- /resources/lizenz-als-text.md: -------------------------------------------------------------------------------- 1 | 2 | CC-BY-SA 4.0 -------------------------------------------------------------------------------- /resources/template.tex: -------------------------------------------------------------------------------- 1 | % --- Default Pandoc header (skip me) --- 2 | 3 | \documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$lang$,$endif$,a4paper]{$documentclass$} 4 | \usepackage[ngerman]{babel} 5 | \usepackage{geometry} 6 | \geometry{a4paper, bottom=40mm} 7 | \usepackage[T1]{fontenc} 8 | \usepackage{lmodern} 9 | \usepackage{longtable} 10 | \usepackage{amssymb,amsmath} 11 | \usepackage{ifxetex,ifluatex} 12 | \usepackage{fixltx2e} % provides \textsubscript 13 | % use microtype if available 14 | \IfFileExists{microtype.sty}{\usepackage{microtype}}{} 15 | \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex 16 | \usepackage[utf8]{inputenc} 17 | $if(euro)$ 18 | \usepackage{eurosym} 19 | $endif$ 20 | \else % if luatex or xelatex 21 | \usepackage{fontspec} 22 | \ifxetex 23 | \usepackage{xltxtra,xunicode} 24 | \fi 25 | \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase} 26 | \newcommand{\euro}{€} 27 | $if(mainfont)$ 28 | \setmainfont{$mainfont$} 29 | $endif$ 30 | $if(sansfont)$ 31 | \setsansfont{$sansfont$} 32 | $endif$ 33 | $if(monofont)$ 34 | \setmonofont{$monofont$} 35 | $endif$ 36 | $if(mathfont)$ 37 | \setmathfont{$mathfont$} 38 | $endif$ 39 | \fi 40 | $if(geometry)$ 41 | \usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} 42 | $endif$ 43 | $if(natbib)$ 44 | \usepackage{natbib} 45 | \bibliographystyle{plainnat} 46 | $endif$ 47 | $if(biblatex)$ 48 | \usepackage{biblatex} 49 | $if(biblio-files)$ 50 | \bibliography{$biblio-files$} 51 | $endif$ 52 | $endif$ 53 | $if(listings)$ 54 | \usepackage{listings} 55 | $endif$ 56 | $if(lhs)$ 57 | \lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} 58 | $endif$ 59 | $if(highlighting-macros)$ 60 | $highlighting-macros$ 61 | $endif$ 62 | $if(verbatim-in-note)$ 63 | \usepackage{fancyvrb} 64 | $endif$ 65 | $if(fancy-enums)$ 66 | % Redefine labelwidth for lists; otherwise, the enumerate package will cause 67 | % markers to extend beyond the left margin. 68 | \makeatletter\AtBeginDocument{% 69 | \renewcommand{\@listi} 70 | {\setlength{\labelwidth}{4em}} 71 | }\makeatother 72 | \usepackage{enumerate} 73 | $endif$ 74 | $if(tables)$ 75 | \usepackage{ctable} 76 | \usepackage{float} % provides the H option for float placement 77 | $endif$ 78 | $if(graphics)$ 79 | \usepackage{graphicx} 80 | % We will generate all images so they have a width \maxwidth. This means 81 | % that they will get their normal width if they fit onto the page, but 82 | % are scaled down if they would overflow the margins. 83 | \makeatletter 84 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth 85 | \else\Gin@nat@width\fi} 86 | $endif$ 87 | \ifxetex 88 | \usepackage[setpagesize=false, % page size defined by xetex 89 | unicode=false, % unicode breaks when used with xetex 90 | xetex]{hyperref} 91 | \else 92 | \usepackage[unicode=true]{hyperref} 93 | \fi 94 | \hypersetup{breaklinks=true, 95 | bookmarks=true, 96 | pdfauthor={$author-meta$}, 97 | pdftitle={$title-meta$}, 98 | colorlinks=true, 99 | urlcolor=$if(urlcolor)$$urlcolor$$else$blue$endif$, 100 | linkcolor=$if(linkcolor)$$linkcolor$$else$magenta$endif$, 101 | pdfborder={0 0 0}} 102 | $if(links-as-notes)$ 103 | % Make links footnotes instead of hotlinks: 104 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 105 | $endif$ 106 | $if(strikeout)$ 107 | \usepackage[normalem]{ulem} 108 | % avoid problems with \sout in headers with hyperref: 109 | \pdfstringdefDisableCommands{\renewcommand{\sout}{}} 110 | $endif$ 111 | \setlength{\parindent}{0pt} 112 | \setlength{\parskip}{6pt plus 2pt minus 1pt} 113 | \setlength{\emergencystretch}{3em} % prevent overfull lines 114 | \providecommand{\tightlist}{% 115 | \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} 116 | $if(numbersections)$ 117 | $else$ 118 | \setcounter{secnumdepth}{0} 119 | $endif$ 120 | $if(verbatim-in-note)$ 121 | \VerbatimFootnotes % allows verbatim text in footnotes 122 | $endif$ 123 | $if(lang)$ 124 | \ifxetex 125 | \usepackage{polyglossia} 126 | \setmainlanguage{$mainlang$} 127 | \else 128 | \usepackage[$lang$]{babel} 129 | \fi 130 | $endif$ 131 | 132 | % --- actual template (stop skipping here) --- 133 | 134 | \setcounter{tocdepth}{2} 135 | 136 | \begin{document} 137 | 138 | \begin{titlepage} 139 | \centering 140 | \vspace*{0em} 141 | \begin{figure}[htbp] 142 | \centering 143 | \includegraphics[width=\maxwidth]{resources/titelbild.png} 144 | \end{figure} 145 | 146 | \vspace{8em} 147 | 148 | \Huge{ridesharing.api 0.1} \\ 149 | \vspace{1em} 150 | \Large{Spezifikation einer einheitlichen Schnittstelle für Mitfahr-Portale.} 151 | 152 | \vspace{10em} 153 | 154 | \Large{Version $detailed-version$} 155 | \Large{$date$} 156 | 157 | \vspace{1em} 158 | 159 | \begin{figure}[htbp] 160 | \centering 161 | \includegraphics[width=71pt,height=26pt]{build/src/images/CC-BY-SA.png} 162 | \end{figure} 163 | \end{titlepage} 164 | 165 | \clearpage 166 | 167 | \hypersetup{linkcolor=blue} 168 | \tableofcontents 169 | \clearpage 170 | 171 | $body$ 172 | 173 | \end{document} -------------------------------------------------------------------------------- /resources/titelbild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/resources/titelbild.png -------------------------------------------------------------------------------- /schema/Calendar.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Calendar", 3 | "description": "Das Objekt beschreibt eine Art Fahrplan, an denen die Mitfahrgelegenheit fährt.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "url" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Calendar" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "trip": { 30 | "description": "Fahrt", 31 | "references": "Trip", 32 | "backreference": "calendar", 33 | "cardinality": "n:1", 34 | "type": "string", 35 | "format": "url" 36 | }, 37 | "calendarException": { 38 | "description": "Ausnahmen zu den definierten Tagen", 39 | "references": "CalendarException", 40 | "backreference": "calendar", 41 | "cardinality": "1:n", 42 | "type": "array", 43 | "items": { 44 | "type": "string", 45 | "format": "url" 46 | } 47 | }, 48 | "start": { 49 | "description": "Beginn der Gültigkeit der angegebenen Wochentage", 50 | "type": "string", 51 | "format": "date" 52 | }, 53 | "end": { 54 | "description": "Ende der Gültigkeit der angegebenen Wochentage", 55 | "type": "string", 56 | "format": "date" 57 | }, 58 | "weekday": { 59 | "description": "Tage, an denen die Fahrt angeboten wird, als Liste an ISO 8601 Wochentag-Nummern.", 60 | "type": "array", 61 | "items": { 62 | "type": "integer" 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /schema/CalendarException.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "CalendarException", 3 | "description": "Das Objekt beschreibt Ausnahmen zu den im Calendar beschriebenen Tagen.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "url" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/CalendarException" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "calendar": { 30 | "description": "Calendar", 31 | "references": "Calendar", 32 | "backreference": "calendarException", 33 | "cardinality": "n:1", 34 | "type": "string", 35 | "format": "url" 36 | }, 37 | "date": { 38 | "description": "Ausnahme-Datum zum bestehenden Calendar", 39 | "type": "string", 40 | "format": "date" 41 | }, 42 | "reason": { 43 | "description": "Grund für die Ausnahme", 44 | "type": "string" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /schema/Car.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Car", 3 | "description": "Das Objekt beschreibt das Fahrzeug.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type" 8 | ], 9 | "properties": { 10 | "id": { 11 | "type": "string", 12 | "format": "url" 13 | }, 14 | "type": { 15 | "type": "string", 16 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Car$" 17 | }, 18 | "created": { 19 | "description": "Zeitpunkt der Erstellung.", 20 | "type": "string", 21 | "format": "date-time" 22 | }, 23 | "modified": { 24 | "description": "Zeitpunkt der letzten Änderung.", 25 | "type": "string", 26 | "format": "date-time" 27 | }, 28 | "trip": { 29 | "description": "Mitfahrgelegenheiten, in welchem das Fahrzeug eingesetzt wird.", 30 | "references": "Trip", 31 | "backreference": "car", 32 | "cardinality": "1:n", 33 | "type": "array", 34 | "items": { 35 | "type": "string", 36 | "format": "url" 37 | } 38 | }, 39 | "singleTrip": { 40 | "description": "Instanzierte Mitfahrgelegenheiten, in welchem das Fahrzeug eingesetzt wird.", 41 | "references": "SingleTrip", 42 | "backreference": "car", 43 | "cardinality": "1:n", 44 | "type": "array", 45 | "items": { 46 | "type": "string", 47 | "format": "url" 48 | } 49 | }, 50 | "owner": { 51 | "description": "Besitzer des Fahrzeugs.", 52 | "references": "Person", 53 | "backreference": "car", 54 | "cardinality": "n:1", 55 | "type": "string", 56 | "format": "url" 57 | }, 58 | "carClass": { 59 | "description": "Art des Fahrzeugs. Hierbei SOLLTE die aus Buchstaben bestehende Klassifizierung verwendet werden, welche in der Verordnung 1400/2002 der EU-Komission beschrieben wird.", 60 | "type": "string" 61 | }, 62 | "capacity": { 63 | "description": "Anzahl der Sitze inkl. Fahrersitz.", 64 | "type": "integer" 65 | }, 66 | "color": { 67 | "description": "Farbe des Fahrzeugs. Der Wert SOLLTE klein geschrieben und in englisch angegeben werden.", 68 | "type": "string" 69 | }, 70 | "year": { 71 | "description": "Baujahr des Fahrzeugs.", 72 | "type": "integer" 73 | }, 74 | "manufacturer": { 75 | "description": "Hersteller des Fahrzeugs.", 76 | "type": "string" 77 | }, 78 | "model": { 79 | "description": "Modell des Fahrzeugs.", 80 | "type": "string" 81 | }, 82 | "licencePlate": { 83 | "description": "Nummernschild des Fahrzeugs.", 84 | "type": "string" 85 | }, 86 | "vin": { 87 | "description": "Vehicle Identification Number.", 88 | "type": "string" 89 | }, 90 | "luggageSuitcaseCapacity": { 91 | "description": "Gepäckkapazität, in großen Koffern gemessen.", 92 | "type": "integer" 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /schema/Location.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Location", 3 | "description": "Das Objekt beschreibt einen geplanten, noch nicht instanzierten Ort.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "name" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Location$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "singleLocation": { 30 | "description": "Instanzierte Location", 31 | "references": "SingleLocation", 32 | "backreference": "location", 33 | "cardinality": "1:n", 34 | "type": "array", 35 | "items": { 36 | "type": "string", 37 | "format": "url" 38 | } 39 | }, 40 | "stop": { 41 | "description": "Haltepunkte", 42 | "references": "Stop", 43 | "backreference": "location", 44 | "cardinality": "1:n", 45 | "type": "array", 46 | "items": { 47 | "type": "string", 48 | "format": "url" 49 | } 50 | }, 51 | "name": { 52 | "description": "Name des Ortes.", 53 | "type": "string" 54 | }, 55 | "streetAddress": { 56 | "description": "Straße und Hausnummer des Ortes.", 57 | "type": "string" 58 | }, 59 | "postalCode": { 60 | "description": "Postleitzahl des Ortes.", 61 | "type": "string" 62 | }, 63 | "subLocality": { 64 | "description": "Untergeordnete Ortsangabe der Anschrift, z.B. Stadtbezirk, Ortsteil oder Dorf.", 65 | "type": "string" 66 | }, 67 | "locality": { 68 | "description": "Stadt oder Ort des Ortes.", 69 | "type": "string" 70 | }, 71 | "geojson": { 72 | "description": "Geodaten-Repräsentation des Orts. Der Wert dieser Eigenschaft muss der Spezifikation von GeoJSON entsprechen, d.h. es muss ein vollständiges Feature-Objekt ausgegeben werden.", 73 | "type": "object" 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /schema/Participation.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Participation", 3 | "description": "Das Objekt beschreibt die Teilnahme an einer Fahrt.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "role", 9 | "status" 10 | ], 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "format": "url" 15 | }, 16 | "type": { 17 | "type": "string", 18 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Participation$" 19 | }, 20 | "created": { 21 | "description": "Zeitpunkt der Erstellung.", 22 | "type": "string", 23 | "format": "date-time" 24 | }, 25 | "modified": { 26 | "description": "Zeitpunkt der letzten Änderung.", 27 | "type": "string", 28 | "format": "date-time" 29 | }, 30 | "board": { 31 | "description": "Haltepunkt, an welchem die Person zusteigt.", 32 | "references": "SingleStop", 33 | "backreference": "participationBoard", 34 | "cardinality": "n:1", 35 | "type": "string", 36 | "format": "url" 37 | }, 38 | "deboard": { 39 | "description": "Haltepunkt, an welchem die Person aussteigt.", 40 | "references": "SingleStop", 41 | "backreference": "participationDeboard", 42 | "cardinality": "n:1", 43 | "type": "string", 44 | "format": "url" 45 | }, 46 | "person": { 47 | "description": "Person.", 48 | "references": "Person", 49 | "backreference": "participation", 50 | "cardinality": "n:1", 51 | "type": "string", 52 | "format": "url" 53 | }, 54 | "role": { 55 | "description": "Rolle des Mitfahrenden. Mögliche Werte sind driver und passenger.", 56 | "type": "string" 57 | }, 58 | "status": { 59 | "description": "Status der Teilnahme an einer Fahrt. Mögliche Werte sind attending, requested und rejected.", 60 | "type": "string" 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /schema/Person.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Person", 3 | "description": "Eine Person.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "role", 9 | "status" 10 | ], 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "format": "url" 15 | }, 16 | "type": { 17 | "type": "string", 18 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Person$" 19 | }, 20 | "created": { 21 | "description": "Zeitpunkt der Erstellung.", 22 | "type": "string", 23 | "format": "date-time" 24 | }, 25 | "modified": { 26 | "description": "Zeitpunkt der letzten Änderung.", 27 | "type": "string", 28 | "format": "date-time" 29 | }, 30 | "route": { 31 | "description": "Routen, die die Person anbietet.", 32 | "references": "Route", 33 | "backreference": "owner", 34 | "cardinality": "1:n", 35 | "type": "array", 36 | "items": { 37 | "type": "string", 38 | "format": "url" 39 | } 40 | }, 41 | "car": { 42 | "description": "Fahrzeuge", 43 | "references": "Car", 44 | "backreference": "owner", 45 | "cardinality": "1:n", 46 | "type": "array", 47 | "items": { 48 | "type": "string", 49 | "format": "url" 50 | } 51 | }, 52 | "participation": { 53 | "description": "Teilnahmen", 54 | "references": "Participation", 55 | "backreference": "person", 56 | "cardinality": "1:n", 57 | "type": "array", 58 | "items": { 59 | "type": "string", 60 | "format": "url" 61 | } 62 | }, 63 | "personContact": { 64 | "description": "Kontaktmöglichkeiten zur Person", 65 | "references": "PersonContact", 66 | "backreference": "person", 67 | "cardinality": "1:n", 68 | "type": "array", 69 | "items": { 70 | "type": "string", 71 | "format": "url" 72 | } 73 | }, 74 | "preferences": { 75 | "description": "Persönliche Präferenzen gegenüber Mitfahrern.", 76 | "references": "Preferences", 77 | "backreference": "person", 78 | "cardinality": "1:n", 79 | "type": "array", 80 | "items": { 81 | "type": "string", 82 | "format": "url" 83 | } 84 | }, 85 | "name": { 86 | "description": "Der vollständige Name der Person mit akademischem Grad und dem gebräuchlichen Vornamen, wie er zur Anzeige durch den Client genutzt werden kann.", 87 | "type": "string" 88 | }, 89 | "affix": { 90 | "description": "Namenszusatz (z.B. jun. oder MdL.)", 91 | "type": "string" 92 | }, 93 | "title": { 94 | "description": "Akademische Titel", 95 | "type": "array", 96 | "items": { 97 | "type": "string" 98 | } 99 | }, 100 | "givenName": { 101 | "description": "Vorname bzw. Taufname.", 102 | "type": "string" 103 | }, 104 | "familyName": { 105 | "description": "Familienname bzw. Nachname.", 106 | "type": "string" 107 | }, 108 | "formOfAddress": { 109 | "description": "Anrede.", 110 | "type": "string" 111 | }, 112 | "gender": { 113 | "description": "Geschlecht. Vorgegebene Werte sind female und male, weitere werden durch die durchgehend klein geschriebene englische Bezeichnung angegeben. Für den Fall, dass das Geschlecht der Person unbekannt ist, sollte die Eigenschaft nicht ausgegeben werden.", 114 | "type": "string" 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /schema/PersonContact.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "PersonContact", 3 | "description": "Eine Kontaktmöglichkeit zu einer Person.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "role", 9 | "status" 10 | ], 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "format": "url" 15 | }, 16 | "type": { 17 | "type": "string", 18 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/PersonContact" 19 | }, 20 | "created": { 21 | "description": "Zeitpunkt der Erstellung.", 22 | "type": "string", 23 | "format": "date-time" 24 | }, 25 | "modified": { 26 | "description": "Zeitpunkt der letzten Änderung.", 27 | "type": "string", 28 | "format": "date-time" 29 | }, 30 | "person": { 31 | "description": "Person, zu dem der Kontakt gehört", 32 | "references": "Participation", 33 | "backreference": "person", 34 | "cardinality": "n:1", 35 | "type": "string" 36 | }, 37 | "contactType": { 38 | "description": "Art der Kontaktmöglichkeit. Vorgegebene Werte sind email, phone, fax, mobile, website. Telefonnummern sollten immer mit internationaler Vorwahl ausgegeben werden (z.B. für Deutsche Nummern also +49123456780 oder 00491234567890).", 39 | "type": "string" 40 | }, 41 | "contactIdentifier": { 42 | "description": "Kontaktmöglichkeit, also z.B. die E-Mail oder die Handynummer.", 43 | "type": "string" 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /schema/Preferences.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Preferences", 3 | "description": "Das Objekt beschreibt die Präferenzen gegenüber Mitfahrern.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type" 8 | ], 9 | "properties": { 10 | "id": { 11 | "type": "string", 12 | "format": "url" 13 | }, 14 | "type": { 15 | "type": "string", 16 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Preferences$" 17 | }, 18 | "created": { 19 | "description": "Zeitpunkt der Erstellung.", 20 | "type": "string", 21 | "format": "date-time" 22 | }, 23 | "modified": { 24 | "description": "Zeitpunkt der letzten Änderung.", 25 | "type": "string", 26 | "format": "date-time" 27 | }, 28 | "person": { 29 | "description": "Person, dem die Einstellungen gehören", 30 | "references": "Person", 31 | "backreference": "person", 32 | "cardinality": "1:1", 33 | "type": "string", 34 | "format": "url" 35 | }, 36 | "nonsmoking": { 37 | "description": "Nichtraucher-Fahrt.", 38 | "type": "boolean" 39 | }, 40 | "gender": { 41 | "description": "Gender der MitfahrerInnen. Es SOLLTEN die Begriffe female fü weiblich und male für männlich verwendet werden. Andere Geschlechter SOLLTEN klein geschrieben und in englisch beschrieben werden.", 42 | "type": "string" 43 | }, 44 | "bike": { 45 | "description": "Fahrradmitnahme erwünscht", 46 | "type": "boolean" 47 | }, 48 | "ageFrom": { 49 | "description": "Mindestalter der MitfahrerInnen, numerischer Wert.", 50 | "type": "integer" 51 | }, 52 | "ageTo": { 53 | "description": "Maximales Alter der MitfahrerInnen, numerischer Wert.", 54 | "type": "integer" 55 | }, 56 | "talkingLevel": { 57 | "description": "Das Level an Gesprächigkeit.", 58 | "type": "float", 59 | "minimum": 0, 60 | "maximum": 1 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /schema/Route.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Route", 3 | "description": "Eine Route, zu der eine oder mehrere Fahrten gehören.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "role", 9 | "status" 10 | ], 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "format": "url" 15 | }, 16 | "type": { 17 | "type": "string", 18 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Route" 19 | }, 20 | "created": { 21 | "description": "Zeitpunkt der Erstellung.", 22 | "type": "string", 23 | "format": "date-time" 24 | }, 25 | "modified": { 26 | "description": "Zeitpunkt der letzten Änderung.", 27 | "type": "string", 28 | "format": "date-time" 29 | }, 30 | "system": { 31 | "description": "System", 32 | "references": "System", 33 | "backreference": "route", 34 | "cardinality": "n:1", 35 | "type": "string", 36 | "format": "url" 37 | }, 38 | "owner": { 39 | "description": "Besitzer der Fahrt. Wert darf nur bei personenbesogenen Exporten ausgegeben werden.", 40 | "references": "Person", 41 | "backreference": "route", 42 | "cardinality": "n:1", 43 | "type": "string", 44 | "format": "url" 45 | }, 46 | "trip": { 47 | "description": "Die zu der Route gehörenden Fahrten.", 48 | "references": "Trip", 49 | "backreference": "route", 50 | "cardinality": "1:n", 51 | "type": "array", 52 | "items": { 53 | "type": "string", 54 | "format": "url" 55 | } 56 | }, 57 | "active": { 58 | "description": "Zeigt an, ob die Fahrt aktuell aktiv ist.", 59 | "type": "boolean" 60 | }, 61 | "published": { 62 | "description": "Zeitpunkt der Veröffentlichung.", 63 | "type": "string", 64 | "format": "date-time" 65 | }, 66 | "expired": { 67 | "description": "Zeitpunkt, ab welchem das Angebot nicht mehr gültig ist.", 68 | "type": "string", 69 | "format": "date-time" 70 | }, 71 | "boardingMinimum": { 72 | "description": "Mindestanteil der Fahrt, die ein Mitfahrender dabei sein muss.", 73 | "type": "float", 74 | "exclusiveMinimum": 0, 75 | "maximum": 1 76 | }, 77 | "boardingAllowedTill": { 78 | "description": "Anteil der Fahrt, ab dem kein Zusteigen mehr erlaubt ist.", 79 | "type": "float", 80 | "exclusiveMinimum": 0, 81 | "maximum": 1 82 | }, 83 | "deboardingAllowedFrom": { 84 | "description": "Anteil der Fahrt, bis zu dem Aussteigen nicht erlaubt ist.", 85 | "type": "float", 86 | "minimum": 0, 87 | "exclusiveMaximum": 1 88 | }, 89 | "maxDetourTime": { 90 | "description": "Maximale Anzahl an Minuten, die der / die FahrerIn zum Einsammeln der Mitfarherer zusätzlich fahren würde.", 91 | "type": "integer" 92 | }, 93 | "maxDetourDistance": { 94 | "description": "Maximale Anzahl an Kilometern, die der / die FahrerIn zum Einsammeln der MitfahrererInnen zusätzlich fahren würde.", 95 | "type": "integer" 96 | }, 97 | "seats": { 98 | "description": "Anzahl der Sitze", 99 | "type": "integer", 100 | "minimum": 1 101 | }, 102 | "nonsmoking": { 103 | "description": "Nichtraucher-Fahrt.", 104 | "type": "boolean" 105 | }, 106 | "bike": { 107 | "description": "Anzahl an Fahrrädern, welche mitgenommen werden können.", 108 | "type": "integer" 109 | }, 110 | "ageFrom": { 111 | "description": "Mindestalter der MitfahrerInnen, numerischer Wert.", 112 | "type": "integer" 113 | }, 114 | "ageTill": { 115 | "description": "Maximales Alter der MitfahrerInnen, numerischer Wert.", 116 | "type": "integer" 117 | }, 118 | "gender": { 119 | "description": "Geschlecht der MitfahrerInnen", 120 | "type": "string", 121 | "enum": [ 122 | "male", 123 | "female", 124 | "any" 125 | ] 126 | }, 127 | "talkingLevel": { 128 | "description": "Das Level an Gesprächigkeit.", 129 | "type": "float", 130 | "minimum": 0, 131 | "maximum": 1 132 | }, 133 | "website": { 134 | "description": "URL der Route auf dem Mitfahr-Portals (Deeplink)", 135 | "type": "string", 136 | "format": "url" 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /schema/SingleLocation.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "SingleLocation", 3 | "description": "Das Objekt beschreibt einen instanzierten Ort.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "name" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/SingleLocation$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "stop": { 30 | "description": "Instanzierte Haltepunkte", 31 | "references": "SingleStop", 32 | "backreference": "location", 33 | "cardinality": "1:n", 34 | "type": "array", 35 | "items": { 36 | "type": "string", 37 | "format": "url" 38 | } 39 | }, 40 | "location": { 41 | "description": "Ursprüngliche noch nicht instanzierte Location, von der die SingleLocation abgeleitet wurde.", 42 | "references": "Location", 43 | "backreference": "singleLocation", 44 | "cardinality": "n:1", 45 | "type": "string", 46 | "format": "url" 47 | }, 48 | "name": { 49 | "description": "Name des Ortes.", 50 | "type": "string" 51 | }, 52 | "streetAddress": { 53 | "description": "Straße und Hausnummer des Ortes.", 54 | "type": "string" 55 | }, 56 | "postalCode": { 57 | "description": "Postleitzahl des Ortes.", 58 | "type": "string" 59 | }, 60 | "locality": { 61 | "description": "Stadt oder Ort des Ortes.", 62 | "type": "string" 63 | }, 64 | "subLocality": { 65 | "description": "Untergeordnete Ortsangabe der Anschrift, z.B. Stadtbezirk, Ortsteil oder Dorf.", 66 | "type": "string" 67 | }, 68 | "geojson": { 69 | "description": "Geodaten-Repräsentation des Orts. Der Wert dieser Eigenschaft muss der Spezifikation von GeoJSON entsprechen, d.h. es muss ein vollständiges Feature-Objekt ausgegeben werden.", 70 | "type": "object" 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /schema/SingleStop.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "SingleStop", 3 | "description": "Das Objekt beschreibt einen instanzierten Haltepunkte einer Fahrt.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "moment" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/SingleStop$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | 30 | "singleTrip": { 31 | "description": "Instanzierter Trip, zu welchem der Stop gehört.", 32 | "references": "SingleTrip", 33 | "backreference": "stop", 34 | "cardinality": "n:1", 35 | "type": "string", 36 | "format": "url" 37 | }, 38 | "singleLocation": { 39 | "description": "Ort.", 40 | "references": "SingleLocation", 41 | "backreference": "stop", 42 | "cardinality": "n:1", 43 | "type": "string", 44 | "format": "url" 45 | }, 46 | "stop": { 47 | "description": "Noch nicht instanzierter Stop, von dem der SingleStop abgeleitet wurde.", 48 | "references": "Stop", 49 | "backreference": "singleStop", 50 | "cardinality": "n:1", 51 | "type": "string", 52 | "format": "url" 53 | }, 54 | "participationStart": { 55 | "description": "Einsteigende Teilnahmen", 56 | "references": "Participation", 57 | "backreference": "start", 58 | "cardinality": "1:n", 59 | "type": "array", 60 | "items": { 61 | "type": "string", 62 | "format": "url" 63 | } 64 | }, 65 | "participationStop": { 66 | "description": "Aussteigende Teilnahmen", 67 | "references": "Participation", 68 | "backreference": "stop", 69 | "cardinality": "1:n", 70 | "type": "array", 71 | "items": { 72 | "type": "string", 73 | "format": "url" 74 | } 75 | }, 76 | "arrival": { 77 | "description": "Geplante Ankunftszeit am Stop.", 78 | "type": "string", 79 | "format": "date-time" 80 | }, 81 | "arrivalInaccuracy": { 82 | "description": "Geplante Ungenauigkeit der Ankunft in Sekunden", 83 | "type": "integer" 84 | }, 85 | "departure": { 86 | "description": "Geplante Abfahrtzeit am Stop.", 87 | "type": "string", 88 | "format": "date-time" 89 | }, 90 | "departureInaccuracy": { 91 | "description": "Geplante Ungenauigkeit der Abfahrt in Sekunden", 92 | "type": "integer" 93 | }, 94 | "boardingAllowed": { 95 | "description": "Status, ob ein Einsteigen erlaubt ist.", 96 | "type": "boolean" 97 | }, 98 | "deboardingAllowed": { 99 | "description": "Status, ob ein Aussteigen erlaubt ist.", 100 | "type": "boolean" 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /schema/SingleTrip.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "SingleTrip", 3 | "description": "Das Objekt beschreibt eine instanzierte, also eine real zu einem Zeitpunkt stattfindende Fahrt.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "url" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/SingleTrip$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "trip": { 30 | "description": "Abstrakter Trip", 31 | "references": "RecurrentTrip", 32 | "backreference": "trip", 33 | "cardinality": "n:1", 34 | "type": "string", 35 | "format": "url" 36 | }, 37 | "car": { 38 | "description": "Fahrzeug", 39 | "references": "Car", 40 | "backreference": "trip", 41 | "cardinality": "n:1", 42 | "type": "string", 43 | "format": "url" 44 | }, 45 | "singleStop": { 46 | "description": "Haltepunkte", 47 | "references": "Stop", 48 | "backreference": "trip", 49 | "cardinality": "1:n", 50 | "type": "array", 51 | "items": { 52 | "type": "string", 53 | "format": "url" 54 | } 55 | }, 56 | "participation": { 57 | "description": "Teilnahmen", 58 | "references": "Participation", 59 | "backreference": "trip", 60 | "cardinality": "1:n", 61 | "type": "array", 62 | "items": { 63 | "type": "string", 64 | "format": "url" 65 | } 66 | }, 67 | "cancelled": { 68 | "description": "Wenn der instanzierte Trip ausfällt, darf er nicht einfach gelöscht werden, sondern bekommt das Attribut cancelled.", 69 | "type": "boolean" 70 | }, 71 | "maxDetourTime": { 72 | "description": "Maximale Anzahl an Minuten, die der / die FahrerIn zum Einsammeln der Mitfahrerer zusätzlich fahren würde. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 73 | "type": "integer" 74 | }, 75 | "maxDetourDistance": { 76 | "description": "Maximale Anzahl an Kilometern, die der / die FahrerIn zum Einsammeln der MitfahrererInnen zusätzlich fahren würde. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 77 | "type": "integer" 78 | }, 79 | "seats": { 80 | "description": "Anzahl der aktuell freien Sitze.", 81 | "type": "integer", 82 | "minimum": 1 83 | }, 84 | "boardingMinimum": { 85 | "description": "Mindestanteil der Fahrt, die ein Mitfahrender dabei sein muss. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 86 | "type": "float", 87 | "exclusiveMinimum": 0, 88 | "maximum": 1 89 | }, 90 | "boardingAllowedFrom": { 91 | "description": "Anteil der Fahrt, bis zu dem Aussteigen nicht erlaubt ist. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 92 | "type": "float", 93 | "minimum": 0, 94 | "exclusiveMaximum": 1 95 | }, 96 | "boardingAllowedTill": { 97 | "description": "Anteil der Fahrt, ab dem kein Zusteigen mehr erlaubt ist. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 98 | "type": "float", 99 | "exclusiveMinimum": 0, 100 | "maximum": 1 101 | }, 102 | "nonsmoking": { 103 | "description": "Nichtraucher-Fahrt. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 104 | "type": "boolean" 105 | }, 106 | "bike": { 107 | "description": "Anzahl an Fahrrädern, welche mitgenommen werden können. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 108 | "type": "integer" 109 | }, 110 | "ageFrom": { 111 | "description": "Mindestalter der MitfahrerInnen, numerischer Wert. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 112 | "type": "integer" 113 | }, 114 | "ageTill": { 115 | "description": "Maximales Alter der MitfahrerInnen, numerischer Wert. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 116 | "type": "integer" 117 | }, 118 | "gender": { 119 | "description": "Geschlecht der MitfahrerInnen. Wird nur ausgegeben, wenn der Wert sich vom Wert in Trip unterscheidet.", 120 | "type": "string", 121 | "enum": [ 122 | "male", 123 | "female", 124 | "any" 125 | ] 126 | }, 127 | "website": { 128 | "description": "Öffentlicher Link auf das Angebot (Deeplink).", 129 | "type": "string", 130 | "format": "url" 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /schema/Stop.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Stop", 3 | "description": "Das Objekt beschreibt einen geplanten, noch nicht instanzierten Haltepunkte einer Fahrt.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "moment" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/Stop$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "trip": { 30 | "description": "Mitfahrgelegenheit, zu welchem der Stop gehört.", 31 | "references": "Trip", 32 | "backreference": "stops", 33 | "cardinality": "n:1", 34 | "type": "string", 35 | "format": "url" 36 | }, 37 | "location": { 38 | "description": "Ort.", 39 | "references": "Location", 40 | "backreference": "stop", 41 | "cardinality": "n:1", 42 | "type": "string", 43 | "format": "url" 44 | }, 45 | "singleStop": { 46 | "description": "Instanzierter Stop", 47 | "references": "Stop", 48 | "backreference": "stop", 49 | "cardinality": "1:n", 50 | "type": "array", 51 | "items": { 52 | "type": "string", 53 | "format": "url" 54 | } 55 | }, 56 | "arrival": { 57 | "description": "Geplante Ankunftszeit am Stop.", 58 | "type": "string", 59 | "format": "date-time" 60 | }, 61 | "arrivalInaccuracy": { 62 | "description": "Geplante Ungenauigkeit der Ankunft in Sekunden", 63 | "type": "integer" 64 | }, 65 | "departure": { 66 | "description": "Geplante Abfahrtzeit am Stop.", 67 | "type": "string", 68 | "format": "date-time" 69 | }, 70 | "departureInaccuracy": { 71 | "description": "Geplante Ungenauigkeit der Abfahrt in Sekunden", 72 | "type": "integer" 73 | }, 74 | "boardingAllowed": { 75 | "description": "Status, ob ein Einsteigen erlaubt ist.", 76 | "type": "boolean" 77 | }, 78 | "deboardingAllowed": { 79 | "description": "Status, ob ein Aussteigen erlaubt ist.", 80 | "type": "boolean" 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /schema/System.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "System", 3 | "description": "Ein ridesharing-api:System-Objekt repräsentiert eine ridesharing.api-Schnittstelle für eine bestimmte ridesharing.api-Version. Es ist außerdem der Startpunkt für Clients beim Zugriff auf einen Server.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "created", 9 | "modified", 10 | "ridesharingApiVersion" 11 | ], 12 | "properties": { 13 | "id": { 14 | "type": "string", 15 | "format": "url" 16 | }, 17 | "type": { 18 | "type": "string", 19 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/System$" 20 | }, 21 | "created": { 22 | "description": "Zeitpunkt der Erstellung.", 23 | "type": "string", 24 | "format": "date-time" 25 | }, 26 | "modified": { 27 | "description": "Zeitpunkt der letzten Änderung.", 28 | "type": "string", 29 | "format": "date-time" 30 | }, 31 | "ridesharingApiVersion": { 32 | "type": "string", 33 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org/1\\.(0|1)\\/$", 34 | "description": "ridesharing.api Version" 35 | }, 36 | "otherRidesharingApiVersions": { 37 | "description": "Andere ridesharing.api Versionen", 38 | "references": "System", 39 | "items": { 40 | "type": "string", 41 | "format": "url" 42 | }, 43 | "type": "array" 44 | }, 45 | "route": { 46 | "description": "Die zu dem System gehörenden Routen. es wird auf eine paginierte externe Objektliste verlinkt.", 47 | "references": "Route", 48 | "backreference": "system", 49 | "cardinality": "1:n", 50 | "type": "array", 51 | "items": { 52 | "type": "string", 53 | "format": "url" 54 | } 55 | }, 56 | "license": { 57 | "description": "Lizenz, unter der durch diese API abrufbaren Daten stehen.", 58 | "type": "string", 59 | "format": "url" 60 | }, 61 | "name": { 62 | "description": "Nutzerfreundlicher Name für das System, mit dessen Hilfe Nutzerinnen und Nutzer das System erkennen und von anderen unterscheiden können.", 63 | "type": "string" 64 | }, 65 | "contactName": { 66 | "description": "Name der Ansprechpartnerin bzw. des Ansprechpartners oder der Abteilung, die über die in contactEmail angegebene Adresse erreicht werden kann.", 67 | "type": "string" 68 | }, 69 | "contactEmail": { 70 | "description": "E-Mail-Adresse für Anfragen zur ridesharing.api-API. Die Angabe einer E-Mail-Adresse dient sowohl NutzerInnen wie auch Entwicklerinnen von Clients zur Kontaktaufnahme mit dem Betreiber.", 71 | "type": "string" 72 | }, 73 | "website": { 74 | "description": "URL der Website des Mitfahr-Portals", 75 | "type": "string", 76 | "format": "url" 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /schema/Trip.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Trip", 3 | "description": "Das Objekt beschreibt eine abstrakte Fahrt, welche sich wöchentlich wiederholen kann.", 4 | "type": "object", 5 | "required": [ 6 | "id", 7 | "type", 8 | "url" 9 | ], 10 | "properties": { 11 | "id": { 12 | "type": "string", 13 | "format": "url" 14 | }, 15 | "type": { 16 | "type": "string", 17 | "pattern": "^https\\:\\/\\/schema\\.ridesharing-api\\.org\\/1\\.0\\/SingleTrip$" 18 | }, 19 | "created": { 20 | "description": "Zeitpunkt der Erstellung.", 21 | "type": "string", 22 | "format": "date-time" 23 | }, 24 | "modified": { 25 | "description": "Zeitpunkt der letzten Änderung.", 26 | "type": "string", 27 | "format": "date-time" 28 | }, 29 | "route": { 30 | "description": "Abstrakte Route, mit der eine oder mehrere Fahrten zusammengefasst werden.", 31 | "references": "Route", 32 | "backreference": "trip", 33 | "cardinality": "n:1", 34 | "type": "string", 35 | "format": "url" 36 | }, 37 | "car": { 38 | "description": "Fahrzeug", 39 | "references": "Car", 40 | "backreference": "trip", 41 | "cardinality": "n:1", 42 | "type": "string", 43 | "format": "url" 44 | }, 45 | "backTrip": { 46 | "description": "Der in die exakt entgegengesetzte Richtung laufende Trip, also die Rückfahrt.", 47 | "references": "Trip", 48 | "backreference": "backTrip", 49 | "cardinality": "1:1", 50 | "type": "string", 51 | "format": "url" 52 | }, 53 | "relatedTrip": { 54 | "description": "Trips, welche in irgendeiner anderen Form mit dem bestehenden Trip in Zusammenhang stehen (z.B. Weiterfahrten).", 55 | "references": "Trip", 56 | "backreference": "backTrip", 57 | "cardinality": "1:n", 58 | "type": "array", 59 | "items": { 60 | "type": "string", 61 | "format": "url" 62 | } 63 | }, 64 | "stop": { 65 | "description": "Haltepunkte", 66 | "references": "Stop", 67 | "backreference": "trip", 68 | "cardinality": "1:n", 69 | "type": "array", 70 | "items": { 71 | "type": "string", 72 | "format": "url" 73 | } 74 | }, 75 | "singleTrip": { 76 | "description": "Die Instanzierungen des abstrakten Trips.", 77 | "references": "SingleTrip", 78 | "backreference": "trip", 79 | "cardinality": "1:n", 80 | "type": "array", 81 | "items": { 82 | "type": "string", 83 | "format": "url" 84 | } 85 | }, 86 | "active": { 87 | "description": "Zeigt an, ob die Fahrt aktuell aktiv ist.", 88 | "type": "boolean" 89 | }, 90 | "published": { 91 | "description": "Zeitpunkt der Veröffentlichung.", 92 | "type": "string", 93 | "format": "date-time" 94 | }, 95 | "expired": { 96 | "description": "Zeitpunkt, ab welchem das Angebot nicht mehr gültig ist.", 97 | "type": "string", 98 | "format": "date-time" 99 | }, 100 | "maxDetourTime": { 101 | "description": "Maximale Anzahl an Minuten, die der / die FahrerIn zum Einsammeln der Mitfahrerer zusätzlich fahren würde. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 102 | "type": "integer" 103 | }, 104 | "maxDetourDistance": { 105 | "description": "Maximale Anzahl an Kilometern, die der / die FahrerIn zum Einsammeln der MitfahrererInnen zusätzlich fahren würde. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 106 | "type": "integer" 107 | }, 108 | "seats": { 109 | "description": "Anzahl der für Mitfahrer verfügbaren Sitze. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 110 | "type": "integer", 111 | "minimum": 1 112 | }, 113 | "boardingMinimum": { 114 | "description": "Mindestanteil der Fahrt, die ein Mitfahrender dabei sein muss. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 115 | "type": "float", 116 | "exclusiveMinimum": 0, 117 | "maximum": 1 118 | }, 119 | "boardingAllowedFrom": { 120 | "description": "Anteil der Fahrt, bis zu dem Aussteigen nicht erlaubt ist. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 121 | "type": "float", 122 | "minimum": 0, 123 | "exclusiveMaximum": 1 124 | }, 125 | "boardingAllowedTill": { 126 | "description": "Anteil der Fahrt, ab dem kein Zusteigen mehr erlaubt ist. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 127 | "type": "float", 128 | "exclusiveMinimum": 0, 129 | "maximum": 1 130 | }, 131 | "nonsmoking": { 132 | "description": "Nichtraucher-Fahrt. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 133 | "type": "boolean" 134 | }, 135 | "bike": { 136 | "description": "Anzahl an Fahrrädern, welche mitgenommen werden können. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 137 | "type": "integer" 138 | }, 139 | "ageFrom": { 140 | "description": "Mindestalter der MitfahrerInnen, numerischer Wert. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 141 | "type": "integer" 142 | }, 143 | "ageTill": { 144 | "description": "Maximales Alter der MitfahrerInnen, numerischer Wert. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 145 | "type": "integer" 146 | }, 147 | "gender": { 148 | "description": "Geschlecht der MitfahrerInnen. Wird nur ausgegeben, wenn der Wert sich vom Wert in Route unterscheidet.", 149 | "type": "string", 150 | "enum": [ 151 | "male", 152 | "female", 153 | "any" 154 | ] 155 | }, 156 | "website": { 157 | "description": "Öffentlicher Link auf das Angebot (Deeplink).", 158 | "type": "string", 159 | "format": "url" 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /schema/strings.yml: -------------------------------------------------------------------------------- 1 | de: 2 | Test.test1: "" 3 | Test:test2: "" -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/scripts/__init__.pyc -------------------------------------------------------------------------------- /scripts/extract_urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | 4 | import sys 5 | import re 6 | 7 | pattern = re.compile("(http[s]*://[^\s>\")\]`]+[^ >\"`.\s])") 8 | 9 | 10 | def extract_urls(path): 11 | with open(path, "rb") as f: 12 | content = f.read() 13 | urls = re.findall(pattern, content) 14 | return urls 15 | 16 | 17 | if __name__ == "__main__": 18 | urls = set() 19 | for path in sorted(sys.argv): 20 | this_urls = extract_urls(path) 21 | for url in this_urls: 22 | urls.add(url) 23 | for url in sorted(list(urls)): 24 | print(url) -------------------------------------------------------------------------------- /scripts/json_schema2markdown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8 -*- 3 | 4 | import argparse 5 | import collections 6 | import glob 7 | import json 8 | import os 9 | 10 | import yaml 11 | 12 | 13 | class RidesharingApi: 14 | # Default properties don't need a description 15 | default_properties = [ 16 | "id", 17 | "type", 18 | "modified", 19 | "created" 20 | ] 21 | 22 | objects = [ 23 | "System", 24 | "Route", 25 | "Trip", 26 | "Calendar", 27 | "CalendarException", 28 | "Stop", 29 | "Location", 30 | "SingleTrip", 31 | "SingleStop", 32 | "SingleLocation", 33 | "Person", 34 | "PersonContact", 35 | "Participation", 36 | "Preferences", 37 | "Car" 38 | ] 39 | 40 | 41 | def type_to_string(prop): 42 | """ 43 | Converts the json descriptions of the type of any attribute into a human- 44 | readable string printed in spec 45 | """ 46 | type = prop["type"] 47 | 48 | # switch over all types 49 | if type == "object": 50 | # Check for embedded objects 51 | if "schema" in prop: 52 | type = type + " (" + prop["schema"][0:-5] + ")" 53 | elif type == "string": 54 | if "format" in prop: 55 | if "references" in prop: 56 | type = prop["format"] + " (" + prop["references"] + ")" 57 | else: 58 | type = prop["format"] 59 | elif type == "array": 60 | subtype = array_type_to_string(prop, type) 61 | 62 | type = type + " of " + subtype 63 | elif type == "boolean": 64 | pass 65 | elif type == "integer": 66 | pass 67 | elif type == "float": 68 | pass 69 | else: 70 | raise Exception("Invalid type: " + type) 71 | 72 | return type 73 | 74 | 75 | def array_type_to_string(prop, type): 76 | items = prop["items"] 77 | subtype = items["type"] 78 | # Let's do recursion the copy&paste way 79 | if items["type"] == "object": 80 | # Check for embedded objects 81 | if "schema" in items: 82 | subtype = subtype + " (" + items["schema"][0:-5] + ")" 83 | elif items["type"] == "string": 84 | if "format" in items: 85 | if "references" in items: 86 | subtype = items["format"] + " (" + items["references"] + ")" 87 | elif "references" in prop: 88 | subtype = items["format"] + " (" + prop["references"] + ")" 89 | else: 90 | subtype = items["format"] 91 | elif items["type"] == "integer": 92 | pass 93 | elif type == "boolean": 94 | pass 95 | elif type == "integer": 96 | pass 97 | else: 98 | raise Exception("Invalid type: " + type) 99 | return subtype 100 | 101 | 102 | def schema_to_md_table(schema, examples_folder): 103 | # Formatting 104 | propspace = 30 105 | typespace = 45 106 | descspace = 80 107 | 108 | # Headline 109 | md = "## " + schema["title"] + "{#entity-" + schema["title"].lower() + "}" + "\n" 110 | 111 | # Summary/Description 112 | md += schema["description"] + "\n\n" 113 | 114 | # Table Header 115 | md += "-" * (propspace + typespace + descspace) + "\n" 116 | md += "Name" + " " * (propspace - len("Name")) 117 | md += "Typ" + " " * (typespace - len("Typ")) 118 | md += "Beschreibung" + " " * (descspace - len("Beschreibung")) + "\n" 119 | md += "-" * (propspace - 1) + " " + "-" * (typespace - 1) + " " + "-" * (descspace) + "\n" 120 | 121 | md += table_body(propspace, schema, typespace) 122 | 123 | # End of Table 124 | md += "-" * (propspace + typespace + descspace) + "\n\n" 125 | 126 | md += json_examples_to_md(examples_folder, schema["title"]) 127 | return md 128 | 129 | 130 | def table_body(propspace, schema, typespace): 131 | md = "" 132 | # A row for each attribute 133 | for prop_name, prop in schema["properties"].items(): 134 | type = type_to_string(prop) 135 | 136 | if "description" in prop.keys(): 137 | description = prop["description"].replace("\n", " ") 138 | elif prop_name in RidesharingApi.default_properties: 139 | description = "" 140 | else: 141 | raise Exception(prop_name + " is missing the description property") 142 | 143 | if prop_name in schema["required"] and description != "": 144 | description = "**ZWINGEND** " + description 145 | 146 | # The actual table row 147 | md += "`" + prop_name + "`" + " " * (propspace - len(prop_name)) + type + " " * ( 148 | typespace - len(type)) + description + "\n\n" 149 | return md 150 | 151 | 152 | def json_examples_to_md(examples_folder, name): 153 | md = "" 154 | filepath = os.path.join(examples_folder, name) 155 | examples = glob.glob(filepath + "-[0-9][0-9].json") 156 | for nr, examplepath in enumerate(examples): 157 | # TODO: localize examples 158 | if len(examples) == 1: 159 | md += "**Beispiel**\n\n" 160 | else: 161 | md += "**Beispiel " + str(nr + 1) + "**\n\n" 162 | 163 | example = json.load(open(examplepath, encoding='utf-8'), object_pairs_hook=collections.OrderedDict) 164 | md += "~~~~ {.json}\n" 165 | md += json.dumps(example, ensure_ascii=False, indent=4) + "\n" 166 | md += "~~~~\n\n" 167 | md += "\pagebreak\n" 168 | md += "\n" 169 | 170 | return md 171 | 172 | 173 | def localize_schema(language, translations_file, schema_file): 174 | """ 175 | Replaces the handlebars/django style templates in the schema files with the translations stored in 176 | `translations_file`. The keys used the templates resemble JSONPath 177 | """ 178 | schema = schema_file.read() 179 | 180 | with open(translations_file) as f: 181 | translations = yaml.load(f, Loader=yaml.FullLoader)[language] 182 | 183 | for key in translations.keys(): 184 | pattern = "{{ " + key + " }}" # Avoid mixing python's and our own template language 185 | if schema.find(pattern): 186 | translation = json.dumps(translations[key], ensure_ascii=False)[1:-1] 187 | schema = schema.replace(pattern, translation) 188 | 189 | return json.loads(schema, object_pairs_hook=collections.OrderedDict) 190 | 191 | 192 | def schema_to_markdown(schema_folder, examples_folder, output_file, language, language_file): 193 | # Avoid missing objects 194 | # NOTE: the schema folder contains all schema files and the translations strings file 195 | assert (len(RidesharingApi.objects) == len(os.listdir(schema_folder)) - 1) 196 | 197 | generated_schema = "" 198 | 199 | for obj in RidesharingApi.objects: 200 | filepath = os.path.join(schema_folder, obj + ".json") 201 | 202 | with open(filepath, encoding='utf-8') as file_handle: 203 | schema_json = localize_schema(language, language_file, file_handle) 204 | 205 | schema = schema_to_md_table(schema_json, examples_folder) 206 | 207 | generated_schema += schema 208 | 209 | with open(output_file, "w", encoding='utf-8') as out: 210 | out.write(generated_schema) 211 | 212 | 213 | if __name__ == "__main__": 214 | parser = argparse.ArgumentParser() 215 | 216 | parser.add_argument("schema_folder") 217 | parser.add_argument("examples_folder") 218 | parser.add_argument("output_file") 219 | parser.add_argument("language") 220 | parser.add_argument("language_file") 221 | 222 | args = parser.parse_args() 223 | 224 | schema_to_markdown(args.schema_folder, args.examples_folder, args.output_file, args.language, args.language_file) 225 | -------------------------------------------------------------------------------- /scripts/json_schema2markdown.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridesharing-api/documentation/b0d669b0d86518d1b4e0db265b1fde7500591afe/scripts/json_schema2markdown.pyc -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | root=$(git rev-parse --show-toplevel) 6 | cd ${root} 7 | 8 | # validate schema and examples 9 | res=$(scripts/validate.py) 10 | if [ $? -gt 0 ] 11 | then 12 | exit 1 13 | fi 14 | 15 | # validate tex-template 16 | # TODO: validate tex with lacheck or chktex -------------------------------------------------------------------------------- /scripts/validate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Checks for that the right atttribute types are used in the examples of the OParl 4 | spec. 5 | 6 | To use this as as library, call validate_object() with the jsob loaded into an 7 | OrderedDict. Requires the schema files of at least version ac8c3b to be in a 8 | `schema/` folder. 9 | 10 | ### Type Mapping 11 | ---------------------------- 12 | | OParl JSON | Python | 13 | ---------------------------- 14 | | object | OrderedDict | 15 | | array | list | 16 | | string | str | 17 | | boolean | bool | 18 | | int | int | 19 | ---------------------------- 20 | """ 21 | 22 | import json 23 | import os 24 | import re 25 | import subprocess 26 | from collections import OrderedDict 27 | 28 | 29 | class Validate: 30 | def __init__(self): 31 | self.schema = self.load_schema() 32 | 33 | @staticmethod 34 | def load_schema(): 35 | schema = {} 36 | 37 | for i in os.listdir("schema"): 38 | if not i.endswith(".json"): 39 | continue 40 | with open(os.path.join("schema", i), encoding='utf-8') as fp: 41 | content = json.load(fp, object_pairs_hook=OrderedDict) 42 | schema[content["title"]] = content 43 | 44 | return schema 45 | 46 | def validate_entry(self, attribute, value, properties, properties_key): 47 | """ 48 | Validates one attribute value pair using the schema information given in 49 | properties. Recursively validates arrays or calls validate_object format 50 | embedded objects. 51 | 52 | :param attribute The key or key with array index of this entry for printing messages 53 | :param value The value of this entry 54 | :param properties The schema for the object from which the entry originates 55 | :param properties_key The key without array index for indexing the properties 56 | :return (bool, [string]) Returns a tuple of a bool stating wether the entry is valid or not and a list of 57 | messages 58 | with the errors or notes 59 | """ 60 | # Skip vendor specific attributes 61 | if ":" in properties_key: 62 | return True, "Note: Not validating the vendor specific attribute " + properties_key 63 | 64 | if properties_key not in properties.keys(): 65 | return False, "The attribute is not defined in the schema" 66 | 67 | properties = properties[properties_key] 68 | 69 | messages_from_embedded_objects = [] 70 | 71 | result = None 72 | property_type = properties["type"] 73 | if property_type == "boolean": 74 | if not type(value) == bool: 75 | result = False, "The type '" + type(value).__name__ + "' was found instead of the expected type 'bool'" 76 | elif property_type == "integer": 77 | if not type(value) == int: 78 | result = False, "The type '" + type(value).__name__ + "' was found instead of the expected type 'int'" 79 | elif property_type == "string": 80 | result = self.property_string(value, properties) 81 | elif property_type == "object": 82 | result = self.property_object(attribute, properties, value) 83 | elif property_type == "array": 84 | result = self.property_array(attribute, properties, value, messages_from_embedded_objects) 85 | else: 86 | result = False, "Invalid json type: " + property_type 87 | 88 | if result: 89 | return result 90 | else: 91 | return True, messages_from_embedded_objects 92 | 93 | def property_object(self, attribute, properties, value): 94 | if not type(value) == OrderedDict: 95 | return False, "The type '" + type( 96 | value).__name__ + "' was found instead of the expected type 'OrderedDict'" 97 | if "schema" not in properties.keys(): 98 | return True, "Note: Not validating " + attribute + " due to the lack of schema" 99 | return self.validate_object(value, attribute, properties["schema"]) 100 | 101 | def property_array(self, attribute, properties, value, messages_from_embedded_objects): 102 | if not type(value) == list: 103 | return False, "The type '" + type(value).__name__ + "' was found instead of the expected type 'list'" 104 | 105 | # Check every element of this list by recursive function calls with some debugging information attached 106 | for i, j in enumerate(value): 107 | valid, message = self.validate_entry(attribute + "[" + str(i) + "]", j, properties, "items") 108 | if not valid: 109 | return valid, message 110 | if message != "": 111 | message = message if type(message) == list else [message] 112 | messages_from_embedded_objects += message 113 | 114 | @staticmethod 115 | def property_string(value, properties): 116 | if not type(value) == str: 117 | return False, "The type '" + type(value).__name__ + "' was found instead of the expected type 'str'" 118 | 119 | # String mean different types of String, defined by format 120 | if "format" in properties.keys(): 121 | subtype = properties["format"] 122 | else: 123 | return True, "" 124 | 125 | if subtype == "url": 126 | url = re.compile("(http[s]*://[^\s>\")\]`]+[^ >\"`.\s])") 127 | if not url.match(value): 128 | return False, "'" + value + "' is not a valid url" 129 | elif subtype == "date": 130 | date = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}$") 131 | if not date.match(value): 132 | return False, "'" + value + "' is not a valid date" 133 | elif subtype == "date-time": 134 | datetime = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}$") 135 | if not datetime.match(value): 136 | return False, "'" + value + "' is not a valid datetime" 137 | else: 138 | return "Invalid string type" 139 | 140 | def validate_object(self, target, embedded_object="", ref=None): 141 | """ 142 | Validates a whole object using validate_entry and prints every error 143 | 144 | :param target The object to be validated 145 | :param embedded_object The key of the corresponding if this is an embedded object. Used for better messages 146 | :param ref The expected type 147 | :return (bool, [string]) Returns a tuple of a bool stating wether the object is valid or not and a list of 148 | messages 149 | with the errors or notes 150 | """ 151 | valid = True 152 | messages = [] 153 | 154 | oparl_type = re.compile(r"^https://schema.oparl.org/1.1/([a-zA-Z]+)$").match(target["type"]).group(1) 155 | schema = self.schema[oparl_type] 156 | 157 | if ref and schema["title"] + ".json" != ref: 158 | messages.append(" - [ ] schema '" + ref + "' doesn't match actual type '" + target["type"] + "'") 159 | valid = False 160 | 161 | for i in schema["required"]: 162 | if i not in target.keys() or not target[i]: 163 | messages.append(" - [ ] Required key " + i + " missing.") 164 | 165 | for attribute, value in target.items(): 166 | attribut_valid, message = self.validate_entry(attribute, value, schema["properties"], attribute) 167 | if attribut_valid: 168 | pass 169 | else: 170 | valid = False 171 | 172 | message = message if type(message) == list else [message] 173 | 174 | for i in message: 175 | if i == "": 176 | continue 177 | 178 | if embedded_object == "": 179 | messages.append(" - [ ] '" + attribute + "': " + i) 180 | else: 181 | messages.append("In the embedded object '" + embedded_object + "': '" + attribute + "': " + i) 182 | 183 | return valid, messages 184 | 185 | def run(self): 186 | all_valid = True 187 | print("Validating " + str(len(os.listdir("examples"))) + " files ...") 188 | for file in os.listdir("examples"): 189 | print("\n#### " + os.path.join("examples", file)) 190 | target = json.load(open(os.path.join("examples", file), encoding='utf-8'), object_pairs_hook=OrderedDict) 191 | valid, messages = self.validate_object(target) 192 | if not valid: 193 | all_valid = False 194 | for i in messages: 195 | print(i) 196 | 197 | if all_valid: 198 | print("\n --- Passed --- \n") 199 | else: 200 | print("\n --- Failed --- \n") 201 | exit(1) 202 | 203 | 204 | def main(): 205 | git_root = subprocess.getoutput("git rev-parse --show-toplevel") 206 | os.chdir(git_root) 207 | Validate().run() 208 | 209 | 210 | if __name__ == "__main__": 211 | main() -------------------------------------------------------------------------------- /setup.tex: -------------------------------------------------------------------------------- 1 | \usepackage{xcolor} 2 | 3 | \lstset{ 4 | basicstyle=\ttfamily, 5 | numbers=left, 6 | numberstyle=\footnotesize, 7 | stepnumber=1, 8 | numbersep=5pt, 9 | backgroundcolor=\color{black!10}, 10 | showspaces=false, 11 | showstringspaces=false, 12 | showtabs=false, 13 | tabsize=2, 14 | captionpos=b, 15 | breaklines=true, 16 | breakatwhitespace=false, 17 | breakautoindent=true, 18 | linewidth=\textwidth 19 | } 20 | 21 | \usepackage{framed} 22 | \usepackage{xcolor} 23 | \let\oldquote=\quote 24 | \let\endoldquote=\endquote 25 | \colorlet{shadecolor}{orange!15} 26 | \renewenvironment{quote}{\begin{shaded*}\begin{oldquote}}{\end{oldquote}\end{shaded*}} 27 | 28 | \catcode`@=11 29 | \let \savecr \@tabularcr 30 | \def\@tabularcr{\savecr\hline} 31 | \catcode`@=12 -------------------------------------------------------------------------------- /src/0-00-00-header.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ridesharing.api 3 | rights: CC BY-SA 4.0 4 | language: de-DE 5 | description: Spezifikation einer einheitlichen Schnittstelle für Mitfahr-Portale. 6 | toc-title: Inhaltsverzeichnis 7 | date: 01.01.2019 8 | --- 9 | -------------------------------------------------------------------------------- /src/1-00-00-einleitung.md: -------------------------------------------------------------------------------- 1 | # Einleitung 2 | 3 | Der Mitfahrmarkt in Deutschland ist stark fragmentiert und hierdurch für 4 | die Nutzer der vielfältigen Vermittlungsangebote nicht ausreichend 5 | transparent. Fahrer und Mitfahrer finden zum Teil nicht oder nur mit 6 | großem Zeitaufwand zusammen. Dieses Problem wollen wir mit einem 7 | offenen Datenstandard und der Perspektive auf ein gemeinsames 8 | Meta-Such-Portal angehen. 9 | -------------------------------------------------------------------------------- /src/1-01-00-zielsetzung.md: -------------------------------------------------------------------------------- 1 | ## Zielsetzung 2 | 3 | Dass Mobilität durch eine bessere Vernetzung verschiedener Verkehrsmittel 4 | schneller, komfortabler und umweltfreundlicher werden kann ist 5 | mittlerweile allgemeiner Konsens. Doch an dem „wie“ scheiden sich oft 6 | die Geister. Eines der Kernprobleme ist, dass fast jeder Anbieter von 7 | Mobilitätslösungen in Deutschland einen anderen Datenstandard nutzt. 8 | 9 | Dies ist auch imbeim Mitfahrmarkt nicht anders. Dabei wäre gerade der 10 | Mitfahrmarkt besonders auf eine Vernetzung angewiesen, wie ein früheres 11 | Forschungsprojekt und die darauf folgenden Entwicklungen des 12 | Mitfahrmarkts in den letzten Jahren verdeutlicht haben. Die große Anzahl 13 | an Vermittlungsangeboten erhöht den Aufwand für das Zusammenfinden und 14 | vermindert mögliche Vermittlungserfolge. Ein Metaportal könnte dieses 15 | roblem lösen, doch dies ist nur möglich, wenn die Daten mindestens 16 | strukturiert, im besten Fall in einem einheitlichen Datenstandard 17 | vorliegen. 18 | 19 | Vergleichbar ist die Situation mit der menschlichen Kommunikation: 20 | Möchte man komplexe Informationen austauschen, so ist es zwangsweise 21 | erforderlich, dass man die Sprache des Anderen beherrscht. Ansonsten 22 | ist Kommunikation nur mit hohem Aufwand und dem Verlust vieler Details 23 | möglich. Genauso verhält es sich in der Mobilität: Eine gut 24 | funktionierende Vernetzung gibt es nur mit gemeinsamer Sprache, also 25 | einem gemeinsamen Datenstandard. 26 | 27 | Wichtig ist, dass es sich dabei um einen offenen Datenstandard handelt, 28 | der ohne Lizenzgebühren einsehbar und nutzbar ist. Die aktuelle 29 | unbefriedigende Situation ist unter anderem auf geschlossene Standards 30 | zurückzuführen – ein Datenaustausch zwischen den Plattformen findet zur 31 | Zeit nahezu nicht statt. Das Internet selbst sollte dabei Vorbild sein: 32 | Nur durch die vielen offenen Standards ist die Vernetzung möglich 33 | geworden. Und auch hier wieder gilt die Analogie zur menschlichen 34 | Sprache: Die Idee, für jede Nutzung eines deutschen Wortes eine 35 | Lizenzgebühr zu verlangen, würde zu Recht als völlig absurd abgetan 36 | werden. 37 | -------------------------------------------------------------------------------- /src/1-02-00-nutzungsszenarien.md: -------------------------------------------------------------------------------- 1 | ## Nutzungsszenarien 2 | 3 | Ein Datenstandard für den Mitfahrmarkt hat zahlreiche Anwendungen. Einige 4 | davon sollen hier exemplarisch vorgestellt werden. 5 | -------------------------------------------------------------------------------- /src/1-03-00-marktumfeld.md: -------------------------------------------------------------------------------- 1 | ## Marktumfeld 2 | 3 | Mit über 50 Mitfahrbörsen und Mitfahrapps gibt es eine Vielzahl an 4 | Angeboten. Unter den Plattformen gibt es nur in Einzelfällen einen 5 | Datenaustausch. Da die Anzahl der Fahrerinnen und Fahrer eher begrenzt 6 | ist, gibt es viele recht leere Mitfahrbörsen. Mitfahrerinnen und 7 | Mitfahrer haben daher häufig das Problem, dass Suchen nach 8 | Mitfahrgelegenheiten sehr mühsam sind, da man etliche Börsen durchgehen 9 | muss, um etwas zu finden. 10 | 11 | FahrerInnen und MitfahrerInnen verlieren 12 | sich schlicht auf der Vielzahl nicht vernetzter Angebote, und dies 13 | sorgt für ein Schrumpfen des gesamten Marktes. 14 | -------------------------------------------------------------------------------- /src/1-03-01-metaportal.md: -------------------------------------------------------------------------------- 1 | ### Metaportal 2 | 3 | Aktuell verlieren sich Fahrt-Anbieter und Mitfahrer von Mitfahrgelegenheiten 4 | auf den über 50 Mitfahrportalen, Apps u.v.m.. Darüber hinaus gibt es 5 | noch zahlreiche interne und / oder analoge Systeme wie Mitfahrbänke. 6 | 7 | Es bräuchte also eine spezielle Suchmaschine, welche Zugriff auf alle 8 | Daten aller Mitfahrbörsen hat und so Nutzer schnell über alle Plattformen 9 | hinweg Fahrten suchen können. Diese Suchmaschine wäre das schon mehrfach 10 | zuvor angesprochene Meta-Portal. 11 | 12 | Ein Meta-Portal würde von möglichst vielen Mitfahr-Plattformen 13 | Daten aggregieren und zusammenfügen. Es hätte lediglich Fahrt-Informationen 14 | und wäre so eine rein anonyme Suchmaschine: Die Kontaktaufnahme würde 15 | weiterhin auf der Plattform geschehen, in welcher der Fahrer die Fahrt 16 | eingestellt hat. 17 | 18 | Darüber hinaus könnte das Metaportal die aggregierten Daten normalisieren, 19 | automatisiert veredeln und wiederum via ridesharing.api zur Verfügung 20 | stellen. Dies würde den Aufwand einer Vernetzung zwischen den Portalen 21 | reduzieren, da Fehler einmal auf der Metaplattform statt bei jedem 22 | Datenimport einer Mitfahrbörse ausgebügelt werden können. Außerdem 23 | könnte das Meta-Portal dieselben Daten auch über moderne Schnittstellen 24 | wie z.B. Websockets bereitstellen, selbst wenn das ursprüngliche 25 | Portal dies nicht kann. 26 | -------------------------------------------------------------------------------- /src/1-03-02-multimodalitaet.md: -------------------------------------------------------------------------------- 1 | ### Multimodalität 2 | 3 | Ridesharing funktioniert dann ganz besonders gut, wenn man es mit dem 4 | öffentlichen Nahverkehr verbindet. Denn gerade, wenn MitfahrerInnen 5 | zwischendurch dazusteigen wollen, hängt vom Wahl des Treffpunktes 6 | die Effizienz der gesamten Fahrt ab. Bei Langstrecken-Fahrten lohnt es 7 | sich oft, einen Treffpunkt außerhalb der Innenstadt zu wählen. Und auch 8 | bei Pendelfahrten lohnt es sich, den Mitfahrenden nicht unbedingt von zu 9 | Hause aus abzuholen. 10 | 11 | Moderne Routing-Engines bringen alles mit, um Mitfahrgelegenheiten 12 | und ÖPNV optimal zu verknüpfen. Um jedoch zu funktionieren, brauchen 13 | Routing-Engines standardisierte Rohdaten. Viele Routing-Engines 14 | funktionieren mit GTFS sowie GTFS-RT, so dass ein Datenstandard für 15 | Mitfahrgelegenheiten bestmöglich in GTFS umformbar sein sollte. 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/1-03-02-profildaten-export.md: -------------------------------------------------------------------------------- 1 | ### Export von Profildaten 2 | 3 | Da es zahlreiche Ridesharing-Plattformen gibt, sollte es eine Möglichkeit 4 | geben, dass Nutzer ihre persönlichen Einstellngen und Präferenzen auf 5 | einer Plattform exportieren und auf einer anderen importieren können. 6 | Mit der DSGVO ist dieses eh wünschenswerte Feature verpflichtend geworden. 7 | 8 | Vielfach scheitert dieses Anliegen aber noch an dem Datenformat, da man 9 | aktuell eher CSVs und Textdateien statt strukturierte Daten bekommt. 10 | Dies kann die ridesharing.api ändern, indem es ein JSON-Datenmodell 11 | definiert und so die Plattformen in weiten Teilen untereinander kompatibel 12 | macht. 13 | 14 | Praktisch sähe das dann so aus, dass ein Nutzer beim Export seiner 15 | Nutzerdaten eine einzige JSON-Datei herunterladen könnte. Wenn die Ziel- 16 | Plattform das unterstützt, könnte der Nutzer die Datei dann auf der Ziel- 17 | Plattform hochladen und hätte so ganz einfach sämtliche Einstellungen 18 | übertragen. 19 | -------------------------------------------------------------------------------- /src/1-04-00-bestehende-schnittstellen.md: -------------------------------------------------------------------------------- 1 | ## Bestehende Standards 2 | 3 | Es gibt bereits eine Reihe an Datenstandards, die sich im Umfeld des 4 | Ridesharing-Marktes bewegen. Grob lassen sich diese in zwei Kategorien 5 | einsortieren: Spezielle Ride-Sharing-Schnittstellen, welche die 6 | besonderen Bedürfnisse des Ridesharings berücksichtigen, und allgemeine 7 | Mobilitäts-Daten-Standards, welche durch eine höhere Abstraktion 8 | Kompatibilität zu anderen Mobilitätsformen herstellen. Eine Auswahl: 9 | 10 | * Dycapo ist das Ergebnis einer Bachelor-Arbeit, welche ein abstraktes 11 | Datenmodell auf JSON-Basis definiert und einen vollwertigen Server mit 12 | vielen Aspekten des Meta-M-Mitfahrportals entwickelt hat. [Die 13 | Dokumentation und der Code sind öffentlich auf Github 14 | einsehbar](https://github.com/dgraziotin/dycapo). 15 | * Das oben erwähnte Forschungsprojekt hat ein sehr umfangreiches 16 | XML-Datenmodell spezifiziert, welches viele wichtige Aspekte anspricht, 17 | aber mittlerweile aufgrund seines Alters von ca. 10 Jahren einer 18 | Aktualisierung bedarf . 19 | * Eine Reihe an plattform-spezifischen Schnittstellen bilden ebenfalls 20 | Mitfahrgelegenheiten ab. Diese werden hier aus Neutralitätsgründen 21 | nicht aufgeführt. Umso mehr wird eingeladen, diese auf Github zu 22 | sammeln und zu diskutieren . 23 | * Die Datenstandards [GTFS](https://developers.google.com/transit/gtfs/) 24 | und [GTFS-RT](https://developers.google.com/transit/gtfs-realtime/) 25 | bieten eine gute und realistische Möglichkeit, Ridesharing-Daten mit 26 | weiteren Mobilitätsdaten wie zum Beispiel dem ÖPNV zu verknüpfen. 27 | Außerdem ist GTFS ein Standard-Import-Format für 28 | Open-Source-Routing-Engines. 29 | * Es sollte darauf geachtet werden, Kompatibilität zum zukünftigen 30 | europäischen Datenstandard für Nahverkehrs-Daten 31 | [NeTEx](http://netex-cen.eu/) zu gewährleisten, da abzusehen ist, dass 32 | in diesem Umfeld Daten und Software entstehen wird, welche auch für den 33 | Mitfahr-Markt von Interesse sind. 34 | -------------------------------------------------------------------------------- /src/1-05-00-nomenklatur.md: -------------------------------------------------------------------------------- 1 | ## Nomenklatur {#nomenklatur} 2 | 3 | ### Zwingende, empfohlene und optionale Anforderungen {#muss_sollte_darf} 4 | 5 | Diese Spezifikation nutzt **müssen**, **können** und **sollten** 6 | in einer eindeutig definierten Art und Weise. Diese ist angelehnt an die 7 | Definitionen der Begriffe MUST, SHOULD und MAY (bzw. MUST NOT, SHOULD NOT und 8 | MAY NOT) aus RFC2119.^[RFC2119 ] 9 | 10 | Die Bedeutung im Einzelnen: 11 | 12 | **müssen**/**muss** bzw. **zwingend**: 13 | 14 | : Die Erfüllung einer so gekennzeichneten Anforderung ist zwingend erforderlich. 15 | 16 | Die Entsprechung in RFC2119 lautet "MUST", "REQUIRED" oder "SHALL". 17 | 18 | **nicht dürfen**/**darf nicht**: 19 | 20 | : Dieses Stichwort kennzeichnet ein absolutes Verbot. 21 | 22 | Die Entsprechung in RFC2119 lautet "MUST NOT" oder "SHALL NOT". 23 | 24 | **sollten**/**sollte** bzw. **empfohlen**: 25 | 26 | : Mit dem Wort **sollten** bzw. **sollte** sind empfohlene Anforderungen gekennzeichnet, 27 | die von jeder Implementierung erfüllt werden sollten. Eine Nichterfüllung 28 | ist als Nachteil zu verstehen, beispielsweise weil die Nutzerfreundlichkeit 29 | dadurch Einbußen erleidet, und sollte daher sorgfältig abgewogen werden. 30 | 31 | Die Entsprechung in RFC2119 lautet "SHOULD" oder "RECOMMENDED". 32 | 33 | **sollten nicht**/**sollte nicht** bzw. **nicht empfohlen**: 34 | 35 | : Diese Formulierung wird verwendet, wenn unter gewissen Umständen Gründe 36 | existieren können, die ein bestimmtes Verhalten akzeptabel oder sogar 37 | nützlich erscheinen lassen, jedoch die Auswirkung des Verhaltens vor 38 | einer entsprechenden Implementierung verstanden und abgewogen werden 39 | sollten. 40 | 41 | Die Entsprechung in RFC2119 lautet "SHOULD NOT" oder "NOT RECOMMENDED". 42 | 43 | **dürfen**/**darf** bzw. **optional**: 44 | 45 | : Mit dem Wort **dürfen** bzw. **darf** oder **optional** sind optionale Bestandteile 46 | gekennzeichnet. Ein Anbieter könnte sich entscheiden, den entsprechenden 47 | Bestandteil aufgrund besonderer Kundenanforderungen zu unterstützen, 48 | während andere diesen Bestandteil ignorieren könnten. Implementierer von 49 | Clients oder Servern **dürfen** in solchen Fällen **nicht** davon ausgehen, dass der 50 | jeweilige Kommunikationspartner den entsprechenden, optionalen Anteil 51 | unterstützt. 52 | 53 | Die Entsprechung in RFC2119 lautet "MAY". 54 | 55 | 56 | ### Geschlechterspezifische Begrifflichkeiten {#geschlechterspezifische-begrifflichkeiten} 57 | 58 | Um bei Begriffen wie Nutzer, Anwender, Betreiber etc. die sonst übliche Dominanz 59 | der männlichen Variante zu vermeiden, werden in diesem Dokument 60 | männliche und weibliche Varianten gemischt. Gemeint sind in allen Fällen 61 | Personen jeglichen Geschlechts. 62 | 63 | 64 | ### Codebeispiele {#codebeispiele} 65 | 66 | Die in diesem Dokument aufgeführten Codebeispiele dienen der Veranschaulichung 67 | der beschriebenen Prinzipien. Es handelt sich um frei erfundene Daten. 68 | 69 | Codebeispiele erheben insbesondere bei JSON-Code nicht den Anspruch auf 70 | syntaktische Korrektheit und Vollständigkeit. Dementsprechend können in 71 | Codebeispielen Auslassungen vorkommen, die mit `...` gekennzeichnet werden. 72 | 73 | 74 | ### Namespace-Präfixe für Objekt- und Datentypen {#namespace-praefixe-fuer-objekt-und-datentypen} 75 | 76 | Bei der Erwähnung von Objekttypen, die in dieser Spezifikation beschrieben 77 | werden, wird in der Regel ein Präfix `ridesharing-api:` vor den Namen gesetzt, z. B. 78 | "ridesharing-api:Trip". Damit soll verdeutlicht werden, dass der Objekttyp 79 | innerhalb der ridesharing.api-Spezifikation gemeint ist. 80 | 81 | Das Präfix `ridesharing-api:` steht hierbei für die folgende Namespace-URL: 82 | 83 | https://schema.ridesharing-api.org/1.1/ 84 | 85 | Dadurch kann eine Typenangabe wie `ridesharing-api:Trip` eindeutig in die 86 | folgende URL übersetzt werden: 87 | 88 | https://schema.ridesharing-api.org/1.1/Trip -------------------------------------------------------------------------------- /src/1-06-00-datenschutz.md: -------------------------------------------------------------------------------- 1 | ## Datenschutz 2 | 3 | Bei all den besprochenen Daten geht es zunächst um nicht 4 | personenbezogene Daten, welche bestenfalls gefahrenlos geteilt werden 5 | können. Konkret bedeutet das, dass alle direkt personenbezogenen Daten 6 | wie E-Mail-Kontakt, postalische Adresse oder Handynummer auf dem Server 7 | des Anbieters verbleiben. Lediglich Informationen über die Fahrt selbst 8 | dürfen freigegeben und weiterverwendet werden. Die Kontaktaufnahme 9 | eines Interessenten muss daher auf dem Server des ursprünglichen 10 | Anbieters geschehen. 11 | 12 | Indem man die Daten verknüpft, sind jedoch trotzdem Rückschlüsse auf 13 | den Fahrtenanbieter möglich. Daher sollte mindestens die Einverständnis 14 | des Fahrtenanbieters abgefragt werden. 15 | 16 | Denn eines ist klar: Datenschutz hat allerhöchste Priorität. Bei dem 17 | Protokoll soll es strukturell unmöglich sein, aus Versehen 18 | personenbezogene Daten zu veröffentlichen. Daher muss klar zwischen 19 | öffentlichen und privaten Daten unterschieden werden: Erstere möchte 20 | man nutzen, zweitere schützen. 21 | -------------------------------------------------------------------------------- /src/1-07-00-autoren.md: -------------------------------------------------------------------------------- 1 | ## Autoren 2 | -------------------------------------------------------------------------------- /src/2-00-prinzipien-und-funktionen-der-schnittstelle.md: -------------------------------------------------------------------------------- 1 | # Prinzipien und Funktionen der Schnittstelle {#prinzipien-und-funktionen-der-schnittstelle} 2 | -------------------------------------------------------------------------------- /src/2-01-00-designprinzipien.md: -------------------------------------------------------------------------------- 1 | ## Designprinzipien {#designprinzipien} 2 | 3 | ### Aufbauen auf gängiger Praxis {#aufbauen-auf-gaengiger-praxis} 4 | 5 | Grundlage für die Erarbeitung der ridesharing.api-Spezifikation in der vorliegenden Version 6 | ist eine Analyse von aktuell (2018 - 2019) in Deutschland etablierten 7 | Ridesharing-Angeboten. Erklärtes Ziel für diese erste 8 | Version ist es, mit möglichst geringem Entwicklungsaufwand auf Seite der Plattformanbieter. 9 | Für die ridesharing.api-Spezifikation wurde sozusagen ein Datenmodell als "gemeinsamer Nenner" 10 | auf Basis der gängigen Praxis konstruiert. 11 | 12 | ### Verbesserung gegenüber dem Status Quo wo möglich {#verbesserung-gegenueber-status-quo} 13 | 14 | Dort, wo es dem Ziel der einfachen Implementierbarkeit und der einfachen Migration 15 | nicht im Weg steht, erlauben sich die Autoren dieser Spezifikation, auch Funktionen 16 | aufzunehmen, die noch nicht als gängige Praxis im Bereich der Ridesharing-Plattformen 17 | bezeichnet werden können oder welche nur von einzelnen Systemen unterstützt werden. 18 | Solche Funktionen sind dann so integriert, dass sie nicht als zwingende Anforderung 19 | gelten. 20 | 21 | Als Beispiel wäre die Fähigkeit zu Websocket-basierten Live-Updates zu nennen. 22 | Diese sind nicht verpflichtend, sind aber eine sinnvolle Erweiterung, die mit demselben 23 | Datenmodell realisierbar sind. 24 | 25 | ### Selbstbeschreibungsfähigkeit {#selbstbeschreibungsfaehigkeit} 26 | 27 | Ausgaben des Servers sollten so beschaffen sein, dass sie für menschliche Nutzerinnen 28 | weitgehend selbsterklärend sein können. Dies betrifft besonders die Benennung von 29 | Objekten und Objekteigenschaften. 30 | 31 | Um den Kreis der Entwicklerinnen und Entwickler, die mit einer ridesharing.api 32 | arbeiten können, nicht unnötig einzuschränken, wird hierbei grundsätzlich und 33 | soweit sinnvoll auf englischsprachige Begrifflichkeiten gesetzt. 34 | 35 | ### Erweiterbarkeit {#erweiterbarkeit} 36 | 37 | Implementierer sollen in der Lage sein, über eine ridesharing.api-konforme Schnittstelle auch 38 | solche Informationen auszugeben, die nicht im Rahmen des ridesharing.api-Schemas abgebildet werden 39 | können. Dies bedeutet zum einen, dass ein System Objekttypen unterstützen und ausliefern 40 | darf, die nicht (oder noch nicht) im ridesharing.api-Schema beschrieben sind. Das bedeutet auch, 41 | dass Objekttypen so um eigene Eigenschaften erweitert werden können, die nicht im ridesharing.api 42 | Schema beschrieben sind. 43 | 44 | Ein weiterer Aspekt betrifft die Abwärtskompatibilität, also die Kompatibilität von 45 | ridesharing.api-Clients mit zukünftigen Schnittstellen. So können beispielsweise zukünftige Erweiterungen 46 | des ridesharing.api-Schemas, etwa um neue Objekttypen, genauso durchgeführt werden, wie die Erweiterungen 47 | um herstellerspezifische Objekttypen. Ein Client muss diese Anteile nicht auswerten, sofern 48 | sie nicht für die Aufgabe des Clients relevant sind. Es bedeutet im Umkehrschluss allerdings auch, dass ein Client 49 | nicht fehlschlagen darf, falls derartige Erweiterungen vorhanden sind. 50 | 51 | 52 | ### Browseability/Verlinkung {#browseability_verlinkung} 53 | 54 | Klassische Webservice-Schnittstellen erfordern von den Entwicklern vollständige Kenntnis 55 | der angebotenen Einstiegspunkte und Zugriffsmethoden, gepaart mit sämtlichen unterstützten 56 | URL-Parametern, um den vollen Funktionsumfang der Schnittstelle ausschöpfen zu können. 57 | 58 | Ridesharing-Angebote sind weitgehend in Form von Graphen aufgebaut. Das bedeutet, dass 59 | Objekte häufig mit einer Vielzahl anderer Objekte verknüpft sind. So hat eine Fahrt mehrere 60 | Stops, an denen Personen in Form einer Participation ein- oder aussteigen. Gleichzeitig können 61 | Personen Autos besitzen, die wiederum bei mehreren angebotenen Fahrten eingesetzt werden. 62 | 63 | Eine ridesharing.api-Schnittstelle gibt jedem einzelnen Objekt eine eindeutige Adresse, eine URL. 64 | Somit kann die Schnittstelle den Verweis von einem Objekt, beispielsweise einem Gremium, 65 | auf ein anderes Objekt, etwa ein Mitglied des Gremiums, dadurch ausgeben, dass im Kontext 66 | des Gremiums die URL des Mitglieds ausgeben wird. Der Client kann somit ausgehend von einem 67 | bestimmten Objekt die zugehörigen Objekte im System finden, indem er einfach den angebotenen 68 | URLs folgt. Dieses Prinzip wird auch "Follow Your Nose"^[] genannt. 69 | -------------------------------------------------------------------------------- /src/2-02-zukunftssicherheit.md: -------------------------------------------------------------------------------- 1 | ## Zukunftssicherheit {#zukunftssicherheit} 2 | 3 | Sollte in Zukunft eine zu ridesharing.api 1.0 inkompatible Version 2.0 erscheinen, kann ein 4 | Server beide Versionen gleichzeitig unterstützen, um mit ridesharing.api 1.0 Clients 5 | kompatibel zu bleiben. Dazu muss der Server die ridesharing.api 2.0-Schnittstelle unter 6 | einer eigenen URL parallel zur bestehenden ridesharing.api 1.0-Schnittstelle anbieten, 7 | siehe Kapitel [System](#system). 8 | -------------------------------------------------------------------------------- /src/2-03-urls.md: -------------------------------------------------------------------------------- 1 | ## URLs {#urls} 2 | 3 | ![Aufbau einer URL](build/src/images/url.png){ width=100% } 4 | 5 | Den URLs (für _Uniform Resource Locators_) kommt eine besondere Bedeutung zu 6 | und es werden deshalb eine Reihe von Anforderungen an deren Aufbau und 7 | Eigenschaften gestellt. Die allgemeine Funktionsweise von URLs ist in RFC 3986 8 | beschrieben^[RFC 3986: ]. 9 | 10 | Grundsätzlich **müssen** alle Zugriffe zustandslos erfolgen können, also ohne 11 | Sessioninformationen wie Cookies. Das bedeutet, dass alle Informationen, 12 | die zum Abrufen eines Objekts nötig sind, in der URL vorhanden sein müssen. 13 | 14 | ### URL-Kanonisierung {#url_kanonisierung} 15 | 16 | Um Objekte eindeutig identifizieren zu können ist es notwendig, dass ein Server 17 | für ein Objekt genau eine unveränderliche URL benutzt. Diese Festlegung auf 18 | genaue eine eindeutige URL wird Kanonisierung genannt. Ein Server **muss** 19 | deshalb für jedes seiner Objekte eine kanonische URL bestimmen können. 20 | 21 | Es wird empfohlen keine IP-Adressen in URLs zu benutzen, sondern einen 22 | mit Bedacht gewählten Hostnamen einzusetzen. Das ist vor allem im Hinblick 23 | auf die Langlebigkeit der URLs wichtig. 24 | 25 | Um die Kanonisierung zu gewährleisten **sollten** ridesharing.api-Server so konfiguriert 26 | werden, dass sie nur über eine bestimmte Domain erreichbar sind. ridesharing.api-Server 27 | **sollten** dagegen möglichst **nicht** nur über eine IP-Addresse sowie möglichst auch **nicht** über weitere, nicht kanonische URLs erreichbar sein. 28 | 29 | Wenn ein Server auch durch eine nicht-kanonische URL erreichbar ist, dann 30 | **sollte** eine entsprechende HTTP-Anfrage mit einer Weiterleitung auf die 31 | entsprechende kanonische URL und HTTP-Status-Code 301 beantwortet werden. 32 | Zur Überprüfung kann z.B. der `Host`-Header einer HTTP-Anfrage verwendet werden. 33 | 34 | Beim Pfad-Bestandteil der URL **müssen** Server-Implementierer darüber hinaus 35 | beachten, dass zur kanonischen Schreibweise auch die Groß- und Kleinschreibung, die Anzahl von Schrägstrichen als Pfad-Trennzeichen und die Anzahl von führenden Nullen vor numerischen URL-Bestandteilen gehört. 36 | 37 | Die Kanonisierung umfasst auch den Query-String-Bestandteil der URL. Wie auch 38 | beim Pfad gilt, dass für jeden Parameter und jeden Wert im Query-String genau 39 | eine kanonische Schreibweise gelten **muss**. 40 | 41 | Darüber hinaus **sollte** der Server-Implementierer darauf achten, Query-String-Parameter 42 | immer nach demselben Prinzip zu sortieren. Als Beispiel: Die beiden URLs 43 | 44 | https://ridesharing.example.org/stops?person=1&trip=2 45 | https://ridesharing.example.org/stops?trip=2&person=1 46 | 47 | unterscheiden sich lediglich in der Reihenfolge der Query-String-Parameter. Da 48 | sie jedoch nicht identisch sind, könnten Clients annehmen, dass beide URLs 49 | verschiedene Objekte repräsentieren. 50 | 51 | Clients **sollen** die vom Server gelieferten URLs bei Anzeige, Speicherung 52 | und Weiterverarbeitung nicht verändern. 53 | 54 | ### HTTP und HTTPS {#http-und-https} 55 | 56 | Der Einsatz des verschlüsselten HTTPS wird empfohlen. Bei Verwendung von HTTPS 57 | wird allen URLs "https://" voran gestellt, ansonsten beginnen URLs mit 58 | "http://". 59 | 60 | Aus Gründen der URL-Kanonisierung ist es **zwingend** notwendig, dass ein 61 | Server-Betreiber sich entweder für HTTP oder für HTTPS entscheidet. 62 | Es jedoch möglich, eine Weiterleitung (HTTP Status-Code 301) 63 | einzurichten. Eine Weiterleitung von HTTPS auf HTTP wird **nicht empfohlen**. 64 | 65 | ### Langlebigkeit {#url_langlebigkeit} 66 | 67 | Weiterhin sollen URLs langlebig sein, sodass sie möglichst lange zur Abfrage des 68 | dazugehörigen Objekts verwendet werden können. 69 | 70 | In URLs **sollten** deshalb nur Eigenschaften des Objekts aufgenommen werden, 71 | die nicht verändert werden. Ändert sich beispielsweise die Kennung einer 72 | Drucksache im Verlauf ihrer Existenz, dann scheidet sie für die Bildung 73 | der URL aus. 74 | 75 | Des Weiteren sollen Eigenschaften der Implementierung nicht sichtbar sein. 76 | Ist ein ridesharing.api-Server beispielsweise in PHP geschrieben, **sollte** dies 77 | **nicht** dazu führen, dass im Pfad ein Bestandteil wie "ridesharing.php/" erscheint. 78 | 79 | Weitere Empfehlungen für langlebige URLs liefern Tim Berners-Lee^[Berners-Lee, Tim: Cool URIs don't change. ] sowie die Europäische Kommission^[Study on persistent URIs, with identification of 80 | best practices and recommendations on the topic for the MSs and the EC. (PDF) ]. 81 | -------------------------------------------------------------------------------- /src/2-04-json-ausgabe.md: -------------------------------------------------------------------------------- 1 | ## JSON-Ausgabe {#json-ausgabe} 2 | 3 | Ein ridesharing.api-Server **muss** Objekte in Form von JSON ausgeben. Die Abkürzung JSON steht 4 | für "JavaScript Object Notation". Das JSON-Format ist in 5 | RFC 7159^[RFC 7159: ] beschrieben. 6 | 7 | Sämtliche JSON-Ausgabe **muss** in UTF-8 ohne Byte Order Mark (BOM) geschehen. Dies entspricht 8 | RFC 7159 Section 8.1[^fn-rfc7159-81]. Gemäß RFC 7159 Section 7[^fn-rfc7159-7] **darf** UTF-8 9 | String-Escaping verwendet werden. XML-/HTML-String-Escaping **darf nicht** verwendet werden. 10 | 11 | Eine Syntaxübersicht und weitere Implementierungshinweise finden sich auf 12 | [json.org](http://json.org/). 13 | 14 | Es ist gestattet, weitere zur JSON-Ausgabe semantisch identische 15 | Formate^[Zu semantisch identischen Formaten zählen u.a.: YAML, MessagePack, etc.] 16 | anzubieten. Da diese jedoch nicht Bestandteil der Spezifikation sind, 17 | **sollten** sich Clients nicht auf deren Vorhandensein verlassen. 18 | 19 | [^fn-rfc7159-7]: [RFC 7159 Section 7](https://tools.ietf.org/html/rfc7159#section-7) 20 | [^fn-rfc7159-81]: [RFC 7159 Section 8.1](https://tools.ietf.org/html/rfc7159#section-8.1) 21 | 22 | ### In der ridesharing.api verwendete Datentypen 23 | 24 | In ridesharing.api werden alle in JSON definierten Dateitypen verwendet: 25 | 26 | object: 27 | : Objects entsprechen der Definition des Objects in RFC 7159 Section 4 28 | 29 | array: 30 | : Arrays entsprechen der Definition des Arrays in RFC 7159 Section 5 31 | 32 | integer: 33 | : Integers entsprechen der Definition des Integer-Parts der Number aus RFC 7159 Section 6 34 | 35 | boolean: 36 | : Booleans entsprechen der Definition von Boolean in RFC 7159 Section 3 37 | 38 | string: 39 | : Strings entsprechen der Definition der Unicode-Strings aus RFC 7159 Section 7 40 | 41 | 42 | In der ridesharing.api werden verschiedene String-Typen verwendet. Wenn von diesen Typen gesprochen wird, 43 | so wird automatisch ein JSON-String vorausgesetzt: 44 | 45 | url: 46 | : Eine URL ist ein String, der entsprechend des [URL-Kapitels](#urls) formatiert wurde. 47 | 48 | url (Object): 49 | : Eine URL mit in Klammern angehängtem Objektname beschreibt eine URL auf eben diesen Objekttypus. 50 | 51 | date: 52 | : Entspricht einem Datum ohne Uhrzeit und ohne Zeitzone, wie sie im folgenden Abschnitt beschrieben werden. 53 | 54 | date-time: 55 | : Entspricht einem Datum und einer Uhrzeit mit Zeitzone, wie sie im folgenden Abschnitt beschrieben werden. 56 | 57 | ### Datums- und Zeitangaben {#datum_zeit} 58 | 59 | Für Datums- und Zeitangaben wird eine Spezialisierung der in ISO 8601 60 | beschriebenen Formate verwendet. 61 | Ein Datum (date) **muss** muss die Form `yyyy-mm-dd` besitzen und ein 62 | Zeitpunkt (date-time) **muss** in der Form `yyyy-mm-ddThh:mm:ss±hh:mm` angegeben werden. 63 | 64 | Beispiel für ein Datum: `1969-07-21` 65 | 66 | Beispiel für einen Zeitpunkt: `1969-07-21T02:56:00+00:00` 67 | 68 | ### `null`-Werte und leere Listen {#null-werte-und-leere-listen} 69 | 70 | JSON erlaubt es grundsätzlich, Eigenschaften mit dem Wert `null` zu versehen. 71 | Eigenschaften **sollten** nicht mit dem Wert `null` ausgegeben werden, wenn zu 72 | einer Eigenschaft keine Daten vorliegen. Obligatorische Eigenschaften 73 | **dürfen nicht** den Wert `null` haben. 74 | 75 | Im Fall von Arrays erlaubt JSON grundsätzlich die Ausgabe von `[]` für leere 76 | Arrays. Wie bei `null` wird auch hier **empfohlen**, auf die Ausgabe einer 77 | Eigenschaft mit dem Wert `[]` zu verzichten, wenn zu einer Eigenschaft keine Daten 78 | vorliegen. Bei obligatorischen Eigenschaften **muss** jedoch eine leere Liste 79 | ausgegeben werden. 80 | 81 | Bei nicht obligatorischen Eigenschaften sollte gleichermaßen auf die 82 | Ausgabe eines leeren Strings `""` verzichtet werden. 83 | -------------------------------------------------------------------------------- /src/2-05-objektlisten-und-paginierung.md: -------------------------------------------------------------------------------- 1 | ## Objektlisten und Paginierung {#objektlisten-und-paginierung} 2 | 3 | Oft wird für ein Attribut kein Wert ausgegeben, sondern ein anderes Objekt oder 4 | eine Liste von Objekten. Dabei kann eine Referenz auf das Objekt bzw. die 5 | Objektliste angegeben werden, oder das Objekt bzw. die Objektlist wird intern 6 | ausgegeben. Beide Verfahren sollen im Folgenden erklärt werden. 7 | Zu beachten ist, dass für jedes Listenattribut festgelegt ist, welches dieser 8 | Verfahren jeweils zu verwenden ist. Diese Information ist den 9 | [Schemadefinitionen](#schema) zu entnehmen. 10 | 11 | ### Referenzierung von Objekten via URL 12 | 13 | Bei der Referenzierung einzelner Objekte wird eine URL angegeben, welche auf 14 | das entsprechende Objekt verweist. Der Typ ist hierbei ein 15 | `string (url: Objekt-ID)`. 16 | Ein Beispiel hierfür ist `location` in `Stop`: 17 | 18 | ~~~~~ {#objektlisten_ex1 .json} 19 | { 20 | "id": "https://ridesharing-api.example-ridesharing.de/stop/1" 21 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 22 | "location": "https://ridesharing-api.example-ridesharing.de/location/1", 23 | [...] 24 | } 25 | ~~~~~ 26 | 27 | Es kann auch eine Liste von Referenzen ausgegeben werden. Der Typ ist in diesem 28 | Fall `array of string (url: Objekt-ID)`. 29 | 30 | Ein Beispiel hierfür ist `stop` in `Trip`: 31 | 32 | ~~~~~ {#objektlisten_ex2 .json} 33 | { 34 | "id": "https://ridesharing-api.example-ridesharing.de/trip/1" 35 | "type": "https://schema.ridesharing-api.org/1.0/Trip", 36 | "stop": [ 37 | "https://ridesharing-api.example-ridesharing.de/stop/1", 38 | "https://ridesharing-api.example-ridesharing.de/stop/2", 39 | [...], 40 | ], 41 | [...] 42 | } 43 | ~~~~~ 44 | 45 | ### Interne Ausgabe von Objekten 46 | 47 | Objekte können auch intern ausgegeben werden. Dabei wird das gesamte Objekt als 48 | Wert eines Attributs angegeben. Ein Beispiel für ein internes Objekt ist 49 | `location` in `ridesharing-api:Stop`: 50 | 51 | ~~~~~ {#objektlisten_ex3 .json} 52 | { 53 | "id": "https://ridesharing-api.example-ridesharing.de/stop/1" 54 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 55 | "location": { 56 | "id": "https://ridesharing-api.example-ridesharing.de/location/1" 57 | "type": "https://schema.ridesharing-api.org/1.0/Location", 58 | [...] 59 | }, 60 | [...] 61 | } 62 | 63 | ~~~~~ 64 | 65 | Ebenso kann eine Liste von Objekten intern ausgegeben werden. Hier das 66 | Beispiel des Attributes `stop` in `ridesharing-api:Trip`. 67 | 68 | ~~~~~ {#objektlisten_ex4 .json} 69 | { 70 | "id": "https://ridesharing-api.example-ridesharing.de/trip/1" 71 | "type": "https://schema.ridesharing-api.org/1.0/Trip", 72 | "stop": [ 73 | { 74 | "id": "https://ridesharing-api.example-ridesharing.de/stop/1" 75 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 76 | }, 77 | { 78 | "id": "https://ridesharing-api.example-ridesharing.de/stop/2" 79 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 80 | }, 81 | [...] 82 | ] 83 | } 84 | ~~~~~ 85 | 86 | Bei der internen Ausgabe von Objekten **darf** der Server keine gelöschten 87 | Objekte ausgeben. 88 | 89 | ### Externe Objektlisten 90 | 91 | Es können auch Referenzen zu sogenannten externen Objektlisten angegeben werden. 92 | Die externe Liste enthält dann die betreffenden Objekte in Form einer 93 | Listenausgabe. Ein Beispiel dafür ist `route` in `ridesharing-api:System`. 94 | 95 | `ridesharing-api:System`: 96 | 97 | ~~~~~ {#objektlisten_ex5a .json} 98 | { 99 | "id": "https://ridesharing-api.example-ridesharing.de/system" 100 | "type": "https://schema.ridesharing-api.org/1.0/System", 101 | "route": "https://ridesharing-api.example-ridesharing.de/routes" 102 | } 103 | ~~~~~ 104 | 105 | Die externe Objektliste: 106 | 107 | ~~~~~ {#objektlisten_ex5b .json} 108 | { 109 | "data": [ 110 | { 111 | "id":"https://ridesharing-api.example-ridesharing.de/route/1", 112 | "type": "https://schema.ridesharing-api.org/1.0/System", 113 | [...] 114 | }, 115 | { 116 | "id":"https://ridesharing-api.example-ridesharing.de/route/2", 117 | "type": "https://schema.ridesharing-api.org/1.0/System", 118 | [...] 119 | }, 120 | [...] 121 | ], 122 | ... 123 | } 124 | ~~~~~ 125 | 126 | 127 | ### Paginierung {#paginierung} 128 | 129 | Für externe Objektlisten ist eine Aufteilung sogenannte *Listenseiten* 130 | vorgesehen, wobei jede Listenseite eine eigene URL erhält. Das dient dazu, 131 | die bei der jeweiligen Anfrage übertragenen Datenmengen und Antwortzeiten zu 132 | begrenzen. 133 | 134 | Die Entscheidung, ob eine externe Objektliste mit Paginierung 135 | ausgegeben wird, liegt allein beim Server. Bei Listen mit mehr als 100 136 | Einträgen wird dies **empfohlen**. 137 | 138 | Ein Server **muss** für eine stabile Sortierung von Listeneinträgen sorgen. Das 139 | heißt, dass die Sortierung der Einträge einem konstanten Prinzip folgt und sich 140 | nicht von Abfrage zu Abfrage ändert. Das kann z.B. durch die Sortierung von 141 | Objekten nach einer eindeutigen und unveränderlichen ID erreicht werden. 142 | 143 | Jede Listenseite **muss** die Attribute folgenden Attribute enthalten: 144 | 145 | - **data** (Array der intern ausgegebenen Objekte) 146 | 147 | - **pagination** (Object) 148 | 149 | - **links** (Object) 150 | 151 | Für `pagination` sind die folgenden Attribute festgelegt, die alle **optional** 152 | sind: 153 | 154 | - `totalElements`: Gibt die Gesamtanzahl der Objekte in der Liste an. Diese Zahl 155 | kann sich unter Umständen bis zum Aufruf der nächsten Listenseiten ändern. 156 | 157 | - `elementsPerPage`: Gibt die Anzahl der Objekte pro Listenseite an. Dieser Wert 158 | muss auf allen Listenseiten bis auf die letzte gleich sein. 159 | 160 | - `currentPage`: Gibt die aktuelle Seitenzahl in der Liste an. 161 | 162 | - `totalPages`: Gibt die Gesamtanzahl der Seiten in der Liste an. 163 | 164 | Für `links` sind folgende Attribute festgelegt, die bis auf `next` alle 165 | **optional** sind: 166 | 167 | - `first`: URL der ersten Listenseite 168 | 169 | - `prev`: URL der vorherigen Listenseite 170 | 171 | - `self`: Die kanonische URL dieser Listenseite 172 | 173 | - `next`: URL der nächsten Listen. Für alle Seiten bis auf die letzte ist die 174 | Angabe dieser URL **zwingend**. 175 | 176 | - `last`: URL der letzten Listenseite 177 | 178 | ~~~~~ {#paginierung_ex1 .json} 179 | { 180 | "data": [ 181 | {...}, 182 | {...}, 183 | ... 184 | ], 185 | "pagination": { 186 | "totalElements": 50000, 187 | "elementsPerPage": 100, 188 | "currentPage": 3, 189 | "totalPages":500 190 | }, 191 | "links": { 192 | "first": "https://ridesharing.example.org/trips/", 193 | "prev": "https://ridesharing.example.org/trips/?page=2", 194 | "self": "https://ridesharing.example.org/trips/?page=3", 195 | "next": "https://ridesharing.example.org/trips/?page=4", 196 | "last": "https://ridesharing.example.org/trips/?page=500", 197 | } 198 | } 199 | ~~~~~ 200 | 201 | ### Filter {#filter} 202 | 203 | Externe Objektlisten können mit den URL-Parametern `created_since`, `created_until`, 204 | `modified_since` und `modified_until` eingeschränkt werden. Diese Parameter 205 | beziehen sich auf die entsprechenden Attribute der jeweiligen Objekte, wobei 206 | reservierte Zeichen URL-Kodiert werden müssen. Ein Server muss diese Parameter 207 | bei allen externen Objektlisten unterstützen. 208 | 209 | Die Filter werden vom Client benutzt, indem die gewünschten URL-Parameter an 210 | die URL der ersten Listenseite angehängt werden. Bei allen weiteren Seiten, 211 | genauer gesagt bei den Werten von `links`, **muss** der Server sicherzustellen, 212 | dass die verwendeten Filter erhalten bleiben. 213 | 214 | Ein Server **muss** für den im nächsten Abschnitt beschrieben 215 | Aktualisierungsmechanismus auch die den Filtern entsprechenden gelöschten 216 | Objekte ausgeben, wenn der Parameter `modified_since` gesetzt ist. 217 | Wenn `modified_since` nicht gesetzt ist, 218 | dann **dürfen** die gelöschten Objekte **nicht** ausgegeben werden. 219 | Dadurch kann sich ein Client effizient darüber informieren, welche der Objekte 220 | in seinem lokalen Bestand gelöscht wurden. 221 | 222 | Lautet die URL für eine Liste von Drucksachen wie folgt: 223 | 224 | https://ridesharing.example.org/trips/ 225 | 226 | kann der Client die folgende URL bilden, um die Ausgabe der Liste auf 227 | Drucksachen einzuschränken, die seit dem 1. Januar 2014 veröffentlicht wurden: 228 | 229 | https://ridesharing.example.org/trips/?created_since=2014-01-01T00%3A00%3A00%2B01%3A00 230 | 231 | Mehrere Parameter können auch gemeinsam verwendet werden. So kann man z.B. eine 232 | Einschränkung vom 1.1.2014 bis zum 31.1.2014 vornehmen: 233 | 234 | https://ridesharing.example.org/trips/?created_since=2014-01-01T00%3A00%3A00%2B01%3A00&created_until=2014-01-31T23%3A59%3A59%2B01%3A00 235 | 236 | Die genannten URL-Parameter erwarten grundsätzlich eine vollständige [`date-time`-Angabe](#datum_zeit). 237 | 238 | Des Weiteren kann ein Client die Anzahl der Objekte pro Listenseite durch 239 | den URL-Parameter `limit` begrenzen, der sich auf das gleichnamige 240 | Attribut bezieht. Ein Client **darf nicht** erwarten, dass sich ein Server an 241 | seine `limit`-Anfrage hält. 242 | 243 | ### Der Aktualisierungsmechanismus {#aktualisierungsmechanismus} 244 | 245 | Der Hauptnutzen der Filter ist die Möglichkeit, einen lokalen Datenbestand 246 | inkrementell zu aktualisieren. 247 | 248 | Ein Client könnte z.B. am 1.1.2014 um 2:00 Uhr deutscher Zeit die Liste aller 249 | Drucksachen herunterladen und in einer Datenbank speichern. 250 | 251 | https://ridesharing.example.org/trips/ 252 | 253 | Um den Datenbestand am nächsten Tag zu aktualisieren, ruft der Client dieselbe 254 | URL auf, diesmal jedoch mit dem Parameter `modified_since` mit dem Wert 255 | `2014-01-01T02:00:00+01:00`. 256 | 257 | https://ridesharing.example.org/trips/?modified_since=2014-01-01T02%3A00%3A00%2B01%3A00 258 | 259 | Diese Liste ist in der Regel deutlich kürzer als die Liste aller Objekte, 260 | sodass die Aktualisierung bedeutend schneller ist als der erste Abruf. Der 261 | Client muss außerdem nur noch eine deutlich kleinere Menge an Objekten in die 262 | Datenbank einfügen, aktualisieren oder löschen, um den gleichen Datenstand wie 263 | der Server zu haben. 264 | -------------------------------------------------------------------------------- /src/2-06-cross-origin-resource-sharing-cors.md: -------------------------------------------------------------------------------- 1 | Cross-Origin Resource Sharing (CORS) {#cors} 2 | ------------------------------------ 3 | 4 | Wenn Webbrowser mittels Skript auf JSON-Ressourcen zugreifen sollen 5 | unterliegen diese Zugriffe üblicherweise einer _Same-Origin-Policy_ (SOP). 6 | Das heißt, eine Anfrage ist nur an den Server zulässig, der auch das 7 | initiierende Skript ausgeliefert hat. Anfragen an andere Server werden 8 | vom Browser blockiert. Diese Einschränkung dient im Allgemeinen 9 | der Sicherheit von Webbrowsern.^[vgl. Wikipedia: Same-Origin-Policy ] 10 | 11 | Um die Daten von ridesharing.api-Servern auch im Kontext von Webanwendungen 12 | flexibel nutzen zu können, ist die Überwindung der SOP nötig. Hierzu dient 13 | _Cross-Origin Resource Sharing_ (CORS)^[Cross Origin Resource Sharing - 14 | W3C Recommendation 16. Januar 2014: ]. Mittels CORS 15 | kann ein Server mitteilen, dass bestimmte von ihm ausgelieferte Ressourcen 16 | auch innerhalb von Webapplikationen genutzt werden dürfen, die nicht vom selben Server ausgeliefert werden. Technisch wird dies durch Ausgabe 17 | zusätzlicher HTTP-Header erreicht. 18 | 19 | ridesharing.api-Server **müssen** für jegliche Anfrage, die mit der Ausgabe von JSON-Daten 20 | beantwortet wird (das sind alle Anfragen außer [Dateizugriffe](#dateizugriff)) 21 | den folgenden HTTP-Antwort-Header senden: 22 | 23 | Access-Control-Allow-Origin: * 24 | 25 | Der HTTP-Antwort-Header `Access-Control-Allow-Methods` **sollte** darüber hinaus 26 | **nicht** gesetzt sein, oder **muss** die Methode `GET` beinhalten. 27 | 28 | Entwicklerinnen von Webanwendungen sollten sich darüber bewusst sein, dass 29 | durch die direkte Einbindung von Skripten Dritter in ihre Anwendungen mögliche 30 | Sicherheitsrisiken entstehen. Für den Fall, dass ein ridesharing.api-Server, etwa in 31 | Folge einer Manipulation, Schadcode ausliefert, könnte dieser unmittelbar 32 | von Skripten im Browser ausgeführt werden. 33 | -------------------------------------------------------------------------------- /src/2-08-geloeschte-objekte.md: -------------------------------------------------------------------------------- 1 | ## Gelöschte Objekte {#geloeschte-objekte} 2 | 3 | In der ridesharing.api **dürfen** Objekte **nicht** einfach gelöscht werden, sodass unter 4 | der betreffenden URL kein gültiges Objekt ausgeliefert wird. Stattdessen 5 | wird ein sogenanntes _soft delete_ verwendet. 6 | 7 | Hintergrund ist, dass ridesharing.api-Clients bei der Aktualisierung ihres 8 | Datenbestandes, z.B. mit den [Filtern](#filter) `modified_since` bzw. 9 | `created_since`, erfahren können müssen, welche Objekte gelöscht wurden. 10 | 11 | Dies wird durch die folgenden Regeln gewährleistet. 12 | 13 | Wenn ein Objekt gelöscht wird, 14 | 15 | * **muss** das Objekt das zusätzliche Attribut `deleted` mit dem Wert 16 | `true` bekommen 17 | * **muss** das Attribut `modified` auf den Zeitpunkt der Löschung setzen 18 | * **müssen** die Attribute `id`, `type` und `created` erhalten bleiben 19 | * **dürfen** alle weiteren Attribute entfernt werden 20 | 21 | Als HTTP-Statuscode **muss** weiterhin 200 verwendet werden. 22 | -------------------------------------------------------------------------------- /src/2-09-ausnahmebehandlung.md: -------------------------------------------------------------------------------- 1 | ## Ausnahmebehandlung {#ausnahmebehandlung} 2 | 3 | Wenn ein Server eine Anfrage nicht bearbeiten kann, z.B. weil die 4 | URL ungültig ist oder das angefragte Objekt nicht existiert, dann **sollte** er 5 | mit dem entsprechenden HTTP-Statuscode antworten. 6 | 7 | Ein Server **sollte** in diesem Fall ein Objekt ausgeben, das die folgenden 8 | 3 Attribute enthält: 9 | 10 | * `type`: Enthält als Wert die URL `https://ridesharing-api.org/1.0/Error` 11 | * `message`: Eine Fehlermeldung, die zur Anzeige für einen Nutzer 12 | gedacht ist. Die Fehlermeldung sollte deshalb in der Sprache der durch die 13 | Schnittstelle ausgelieferten Inhalte verfasst sein 14 | * `debug`: Zusätzliche Informationen über den Fehler 15 | 16 | Wenn ein Server ein solches Objekt ausgibt, dann **muss** er dazu einen 17 | HTTP-Statuscode senden, der einen Fehler anzeigt. 18 | 19 | Ein Client **darf nicht** voraussetzen, dass er im Fall eines Fehlers 20 | verwertbare Informationen wie das oben beschriebene Fehlerobjekt erhält. 21 | -------------------------------------------------------------------------------- /src/2-10-endpunkt.md: -------------------------------------------------------------------------------- 1 | ## ridesharing.api Endpunkt 2 | 3 | Als ridesharing.api Endpunkt bzw. Einsprungspunkt zur Schnittstelle wird ein `ridesharing.api:System` 4 | Objekt genutzt. Falls auf einem HTTP-Host mehrere ridesharing.api-Schnittstellen oder 5 | mehrere ridesharing.api Versionen parallel installiert sind, **müssen** diese eindeutige und 6 | voneinander unabhängige ridesharing.api-Endpunkte anbieten. Es ist allerdings möglich, 7 | eine Liste von `ridesharing.api:System`-Objekten auszugeben, die z.B. auf verschiedene 8 | ridesharing.api-Versionen einer Schnittstelle verweisen. 9 | -------------------------------------------------------------------------------- /src/2-11-instanzierung.md: -------------------------------------------------------------------------------- 1 | ## Instanzierung 2 | 3 | Ein wichtiger Aspekt im Datenstandard ist die Instanzierung. Damit ist 4 | gemeint, dass auch Mitfahrbörsen ganz analog zum ÖPNV einen Soll- und 5 | einen Ist-Fahrtplan haben. 6 | 7 | Die allermeisten Börsen haben zur Zeit nur einen Soll-Fahrplan, den 8 | Börsen ist meistens nicht bekannt, ob eine konkrete Fahrt wirklich 9 | stattfindet. Dafür hat ein relevanter Teil der Mitfahrangebote eine Art 10 | Fahrtplan: die Fahrten finden nicht nur ein mal statt, sondern 11 | regelmäßig. Im Falle einer regionalen Fahrt ist dies zum Beispiel 12 | eine tägliche Fahrt zur Arbeit, im Falle einer überregionalen Fahrt 13 | Wochenend-Pendler, die jedes Wochenende an einen anderen Ort fahren. 14 | 15 | Die ridesharing.api bildet die Instanzierung über die Single*-Objekte 16 | ab. `Trip`, `Stop` und `Location` repräsentieren den Soll-Fahrplan 17 | und haben daher keine Festlegung auf ein Datum, sondern nur auf eine 18 | Zeit und einen Rythmus. `SingleTrip`, `SingleStop` und `SingleLocation` 19 | dagegen repräsentieren konkrete Fahrten und haben daher ein 20 | exaktes Datum sowie exakte Uhrzeiten. 21 | 22 | Durch die Verlinkung via `id`s können Update der instanzierten 23 | Datensätze auch einzeln vorgenommen werden, so dass die Serverlast 24 | gering gehalten werden kann. 25 | -------------------------------------------------------------------------------- /src/3-00-schema.md: -------------------------------------------------------------------------------- 1 | # Schema {#schema} 2 | 3 | Dieses Kapitel beschreibt das Schema der ridesharing.api. Das Schema definiert 4 | die Objekttypen und ihre Eigenschaften. Darüber hinaus ist im Schema 5 | auch festgelegt, in welcher Beziehung verschiedene Objekttypen zu 6 | einander stehen. 7 | 8 | ![ridesharing.api Objekttypen: Ein Überblick. Die Zahl an den Verbindungslinien entspricht der Anzahl der Attribute, die eine oder mehrere Verknüpfungen herstellen.](build/src/images/uml.png){ width=100% } 9 | 10 | ## Die Objekte {#objekttypen} 11 | 12 | Die ridesharing.api nutzt folgenden Objekte: 13 | 14 | * ridesharing.api:System 15 | * ridesharing.api:Route 16 | * ridesharing.api:Trip 17 | * ridesharing.api:Calendar 18 | * ridesharing.api:CalendarException 19 | * ridesharing.api:Stop 20 | * ridesharing.api:Location 21 | * ridesharing.api:SingleTrip 22 | * ridesharing.api:SingleStop 23 | * ridesharing.api:SingleLocation 24 | * ridesharing.api:Person 25 | * ridesharing.api:PersonContact 26 | * ridesharing.api:Participation 27 | * ridesharing.api:Preferences 28 | * ridesharing.api:Car 29 | 30 | Grundsätzlich muss jedes Objekt unter seiner ID abrufbar sein - auch dann, wenn 31 | das Objekt in anderen Objekten intern ausgegeben wird. Bei der internen Ausgabe 32 | wird beim internen Objekt auf die Rückreferenz auf das Elternobjekt verzichtet. 33 | 34 | Als Beispiel hier eine Ausgabe von `ridesharing-api:Trip`, in welchem ein `ridesharing-api:Stop` enthalten 35 | ist: 36 | 37 | ~~~~~ {#objekte_example1 .json} 38 | { 39 | "id": "https://api.meine-mitfahrboerse.de/trip/123", 40 | "type": "https://schema.ridesharing-api.org/1.0/Trip", 41 | "created": "2019-02-07T18:28:18", 42 | "modified": "2019-03-14T15:09:26", 43 | "active": true, 44 | "seats": 3, 45 | "website": "https://meine-mitfahrboerse.de/trip/123", 46 | "route": "https://api.meine-mitfahrboerse.de/route/456", 47 | "stop": [ 48 | { 49 | "id": "https://api.meine-mitfahrboerse.de/stop/789", 50 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 51 | "created": "2019-02-07T18:28:18", 52 | "modified": "2019-03-14T15:09:26", 53 | "arrival": "10:00:00", 54 | "departure": "10:10:00", 55 | "location": { 56 | "id": "https://api.meine-mitfahrboerse.de/stop/012", 57 | "type": "https://schema.ridesharing-api.org/1.0/Location", 58 | "name": "Lyonesse Bahnhof" 59 | } 60 | }, 61 | { 62 | "id": "https://api.meine-mitfahrboerse.de/stop/345", 63 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 64 | "created": "2019-02-07T18:28:18", 65 | "modified": "2019-03-14T15:09:26", 66 | "arrival": "12:00:00", 67 | "departure": "12:10:00", 68 | "location": { 69 | "id": "https://api.meine-mitfahrboerse.de/stop/678", 70 | "type": "https://schema.ridesharing-api.org/1.0/Location", 71 | "name": "Atlantis Hafen" 72 | } 73 | }, 74 | [...] 75 | ] 76 | } 77 | ~~~~~ 78 | 79 | Das enthaltene `ridesharing-api:Stop` muss auch einzeln abgerufen werden können. Dabei kommt 80 | dann das Eltern-Objekt als zusätzliches Attribut hinzu.: 81 | 82 | ~~~~~ {#objekte_example2 .json} 83 | { 84 | "id": "https://api.meine-mitfahrboerse.de/stop/789", 85 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 86 | "created": "2019-02-07T18:28:18", 87 | "modified": "2019-03-14T15:09:26", 88 | "arrival": "10:00:00", 89 | "departure": "10:10:00", 90 | "trip": "https://api.meine-mitfahrboerse.de/trip/123", 91 | "location": { 92 | "id": "https://api.meine-mitfahrboerse.de/stop/012", 93 | "type": "https://schema.ridesharing-api.org/1.0/Location", 94 | "name": "Lyonesse Bahnhof" 95 | } 96 | } 97 | ~~~~~ 98 | 99 | Die im ersten Beispiel gezeigte Liste kann auch als Liste an URLs 100 | geschrieben werden: 101 | 102 | ~~~~~ {#objekte_example3 .json} 103 | { 104 | "id": "https://api.meine-mitfahrboerse.de/trip/123", 105 | "type": "https://schema.ridesharing-api.org/1.0/Trip", 106 | "created": "2019-02-07T18:28:18", 107 | "modified": "2019-03-14T15:09:26", 108 | "active": true, 109 | "seats": 3, 110 | "website": "https://meine-mitfahrboerse.de/trip/123", 111 | "route": "https://api.meine-mitfahrboerse.de/route/456", 112 | "stop": [ 113 | "https://api.meine-mitfahrboerse.de/stop/789", 114 | "https://api.meine-mitfahrboerse.de/stop/345", 115 | [...] 116 | ] 117 | } 118 | ~~~~~ 119 | 120 | ## Übergreifende Aspekte {#uebergreifende-aspekte} 121 | 122 | ### Vollständigkeit {#schema-vollstaendigkeit} 123 | 124 | Alle regulär öffentlich abrufbaren Informationen **sollten** auch in der ridesharing.api 125 | ausgegeben werden, solange dies nicht den Datenschutzbestimmungen widerspricht. 126 | Daher sind sämtliche Felder im Schema als **empfohlen** zu behandeln, wenn 127 | nicht explizit etwas anderes angegeben wurde. 128 | 129 | ### Herstellerspezifische Erweiterungen {#herstellerspezifische-erweiterungen} 130 | 131 | In der ridesharing.api können zusätzliche, herstellerspezifische Eigenschaften hinzugefügt werden. 132 | Dazu wird diesen Eigenschaften ein Herstellerprefix vorangestellt. So könnte man z.B. 133 | `ridesharing-api:Location` um einen Point of Interest erweitern: 134 | 135 | ~~~~~ 136 | "BeispielHersteller:pointOfInterest": "Altes Stadttor", 137 | ~~~~~ 138 | 139 | ### URL-Pfade in den Beispielen {#url-pfade-in-den-beispielen} 140 | 141 | ridesharing.api-Clients wissen nichts vom Aufbau von Pfaden innerhalb von URLs, 142 | müssen dies nicht wissen, und es gibt deshalb in der ridesharing.api-Spezifikation 143 | keine Festlegungen dazu. Die in den Beispielen verwendeten URLs zeigen einen 144 | möglichen Weg zur Umsetzungen der Empfehlungen in URLs. 145 | -------------------------------------------------------------------------------- /src/3-01-eigenschaften-mit-verwendung-in-mehreren-objekttypen.md: -------------------------------------------------------------------------------- 1 | ## Eigenschaften mit Verwendung in mehreren Objekttypen {#eigenschaften-mit-verwendung-in-mehreren-objekttypen} 2 | 3 | ### `id` {#eigenschaft-id} 4 | 5 | Die Eigenschaft `id` enthält den eindeutigen Bezeichner des Objekts, nämlich seine URL. 6 | Dies ist ein **zwingendes** Merkmal für jedes Objekt. 7 | 8 | ### `type` {#eigenschaft-type} 9 | 10 | Objekttypenangabe des Objekts, **zwingend** für jedes Objekt. Der Wert ist 11 | eine Namespace-URL. Für die ridesharing.api-Objekttypen sind die folgenden URLs 12 | definiert: 13 | 14 | Typ (kurz) | Namespace-URL 15 | ------------------------------------|---------------------------------------------------- 16 | `ridesharing-api:Calendar` |https://schema.ridesharing-api.org/1.0/Calendar 17 | `ridesharing-api:CalendarException` |https://schema.ridesharing-api.org/1.0/CalendarException 18 | `ridesharing-api:Car` |https://schema.ridesharing-api.org/1.0/Car 19 | `ridesharing-api:Location` |https://schema.ridesharing-api.org/1.0/Location 20 | `ridesharing-api:Participation` |https://schema.ridesharing-api.org/1.0/Participation 21 | `ridesharing-api:Person` |https://schema.ridesharing-api.org/1.0/Person 22 | `ridesharing-api:PersonContact` |https://schema.ridesharing-api.org/1.0/PersonContact 23 | `ridesharing-api:Preferences` |https://schema.ridesharing-api.org/1.0/Preferences 24 | `ridesharing-api:Route` |https://schema.ridesharing-api.org/1.0/Route 25 | `ridesharing-api:SingleLocation` |https://schema.ridesharing-api.org/1.0/SingleLocation 26 | `ridesharing-api:SingleStop` |https://schema.ridesharing-api.org/1.0/SingleStop 27 | `ridesharing-api:SingleTrip` |https://schema.ridesharing-api.org/1.0/SingleTrip 28 | `ridesharing-api:Stop` |https://schema.ridesharing-api.org/1.0/Stop 29 | `ridesharing-api:System` |https://schema.ridesharing-api.org/1.0/System 30 | `ridesharing-api:Trip` |https://schema.ridesharing-api.org/1.0/Trip 31 | 32 | ### `created` {#eigenschaft-created} 33 | 34 | Datum und Uhrzeit der Erstellung des jeweiligen Objekts. 35 | 36 | Diese Eigenschaft **muss** in allen Objekttypen angegeben werden. 37 | 38 | ### `modified` {#eigenschaft-modified} 39 | 40 | Diese Eigenschaft kennzeichnet stets Datum und Uhrzeit der letzten Änderung des 41 | jeweiligen Objekts. 42 | 43 | Diese Eigenschaft **muss** - genau wie `created` - in allen Objekttypen angegeben 44 | werden. 45 | 46 | Es ist **zwingend**, dass bei jeder Änderung eines Objekts der Wert dieses 47 | Attributs auf die zu diesem Zeitpunkt aktuelle Uhrzeit gesetzt wird, da ein 48 | Client in der Regel seinen Datenbestand nur auf Basis dieses Attributs 49 | verlustfrei aktualisieren kann. 50 | 51 | ### `deleted` {#eigenschaft-deleted} 52 | 53 | Falls das Objekt gelöscht wurde, muss dieses gemäß Kapitel 2.8 das Attribut 54 | `deleted: true` bekommen. 55 | -------------------------------------------------------------------------------- /src/4-00-sucherweiterung.md: -------------------------------------------------------------------------------- 1 | # Sucherweiterung {#sucherweiterung} 2 | 3 | Die `ridesharing.api` ist konzipiert, um volle Datenbestände abzurufen und 4 | aktuell zu halten. Hintergrund ist, dass nur mit dem Vorliegen aller Daten 5 | wünschenswerte Features wie multimodales Routing umgesetzt werden können. 6 | Daher wird empfohlen, immer diesen Weg bereitzustellen. 7 | 8 | Da zum Beispiel aber bei Browserabfragen lediglich ein nutzerspezifisches 9 | Subset an Informationen gebraucht wird, soll im Folgenden skiziert werden, 10 | wie man leicht auf dem `ridesharing.api` Datenmodell Abfragen erstellen kann. 11 | 12 | ## Suchanfrage 13 | 14 | In den allermeisten Fällen wird nach einem `SingleTrip` gesucht, da üblicherweise 15 | eine spezifische Fahrt von A nach B gesucht wird. Darüber hinaus wird 16 | im Fall von regelmäßigen Fahrten ein abstrakterer `Trip` gesucht. Die Suchanfragen 17 | unterscheiden sich wie bei `SingleTrip` vs. `Trip` vor allem darin, dass 18 | beim `SingleTrip` eine konkrete Abfahrtzeit gesucht wird, beim `Trip` dagegen 19 | aber ein sich wiederholendes Muster wie z.B. "jeden Wochentag 7:00 Uhr". 20 | 21 | Unsere Such-API muss also für beide Basis-Objekte funktionieren, was angesichts 22 | der Ähnlichkeit beider Objekte recht einfach ist. 23 | 24 | Um eine Suche zu starten, muss ein mit den Wünschen des Anfragenden ausgefüllter 25 | `Trip` bzw. `SingleTrip` an den Server gesendet werden. Relevant bei dieser 26 | Anfrage sind zudem die `Stop`s bzw. `SingleStop`s und ihre dazugehörigen 27 | `Location` bzw `SingleLocation`. 28 | 29 | Um alles in einen Request zu verpacken, wird die in Kapitel 2.5.3 30 | dokumentierte interne Ausgabe von Objekten verwendet. Eine so aufgebaute 31 | Suchanfrage sähe zum Beispiel also wie folgt aus: 32 | 33 | 34 | ~~~~~ {#search_request_1.json} 35 | { 36 | "type": "https://schema.ridesharing-api.org/1.0/SingleTrip", 37 | "nonsmoking": true, 38 | "singleStop": [ 39 | { 40 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 41 | "departure": "2019-03-14T15:09:26", 42 | "singleLocation": { 43 | "streetAddress": "Am Geldspeicher 10", 44 | "postalCode": "12354", 45 | "locality": "Entenhausen" 46 | } 47 | }, 48 | { 49 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 50 | "singleLocation": { 51 | "streetAddress": "Am großen Pilz 5", 52 | "postalCode": "54321", 53 | "locality": "Schlumpfhausen" 54 | } 55 | } 56 | ] 57 | } 58 | ~~~~~ 59 | 60 | Ebenfalls möglich ist eine Suche über Längen- und Breitengrad: 61 | 62 | ~~~~~ {#search_request_1.json} 63 | { 64 | "type": "https://schema.ridesharing-api.org/1.0/SingleTrip", 65 | "nonsmoking": true, 66 | "singleStop": [ 67 | { 68 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 69 | "departure": "2019-03-14T15:09:26", 70 | "singleLocation": { 71 | "geojson": { 72 | "type": "Feature", 73 | "geometry": { 74 | "type": "Point", 75 | "coordinates": [49, 09] 76 | } 77 | } 78 | } 79 | }, 80 | { 81 | "type": "https://schema.ridesharing-api.org/1.0/Stop", 82 | "singleLocation": { 83 | "geojson": { 84 | "type": "Feature", 85 | "geometry": { 86 | "type": "Point", 87 | "coordinates": [50, 10] 88 | } 89 | } 90 | } 91 | } 92 | ] 93 | } 94 | ~~~~~ 95 | 96 | Dieselbe Art der Anfrage ist auch auf `Trip` mäglich und lässt sich beliebig 97 | durch Zusätze a la `Calendar` zur Definition von regelmäßigen Fahrten erweitern. 98 | 99 | ## Suchantwort 100 | 101 | Eine Antwort auf eine Suchanfrage findet ebenfalls als Trip bzw SingleTrip 102 | mit intern ausgegebenen Objekten statt. Da die Ausgabe zu 100% der normalen 103 | `ridesharing.api` Ausgabe enstpricht, verzichten wir hier auf ein Beispiel. 104 | 105 | Wenn mit der Suchabfrage mehrere Server abgefragt werden, so wird es zwangsweise 106 | zu sehr unterschiedlichen Ergebnissen kommen: jeder Dienst wird eine andere 107 | Form der Georeferenzierung und der Priorisierung haben. Diese zwangsweise 108 | auftretende Ungenauigkeit sollte beim Entwicklen berücksichtigt werden und 109 | als Empfehlung zum vollständigen Bereitstellen der Daten verstanden werden: 110 | nur so lassen sich konsistente Suchergebnisse erzeugen. 111 | -------------------------------------------------------------------------------- /src/images/CC-BY-SA.svg: -------------------------------------------------------------------------------- 1 | 2 | ]> 3 | 4 | Creative Commons “Attribution-Share Alike” license icon 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/images/objekttypen_graph.dot: -------------------------------------------------------------------------------- 1 | graph distributed { 2 | graph [nodesep=0.5, dpi=300] 3 | node [fontname="Helvetica"] 4 | edge [color="Gray"] 5 | System -- Trip; 6 | Trip -- Stop; 7 | Stop -- Location; 8 | Participation -- Stop[label="2x"]; 9 | Participation -- Trip; 10 | Trip -- RecurrentTrip; 11 | Trip -- Preferences; 12 | Person -- Car; 13 | Trip -- Car; 14 | Person -- Participation 15 | } --------------------------------------------------------------------------------