#lang scheme/base ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SHP: Hypertext Processor ;; ;; a PHP like web framework for PLT Scheme ;; ;; Bonzai Lab, LLC. All rights reserved. ;; ;; Licensed under LGPL. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; xmlrpc.ss - xmlrpc converter (from xexpr/sxml) and generator. ;; yc 7/7/2010 - first version. (require "base.ss" "depend.ss" "request.ss" "response.ss" (planet bzlib/date:1:3/srfi) ) ;; xmlrpc spec - http://www.xmlrpc.com/spec ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GENERATOR ;; we want to generate xmlrpc based on the type of the value - cond-registry will handle the registration of the types (define xmlrpc (make-cond-registry)) ;; based on the xmlrpc spec, the following are the only scalar types: ;; int (it can be i4 or i8 - but since scheme has arbitrary numeric tower we will just return int) ;; double (we need to split from scheme's number into either int or double) ;; boolean ;; string ;; dateTime.iso8601 ;; base64 - we current do not support base64 generation until we understand the use case better ;; make-scalar-type simplifies the converter generation for each of the scalar types. ;; (->* (symbol?) ((-> any/c string?)) (-> any/c xexpr?)) (define (make-scalar-type type (convert (lambda (v) (format "~a" v)))) (lambda (v) `(,type ,(convert v)))) ;; register the base scalar types. ;; integer? (registry-set! xmlrpc integer? (make-scalar-type 'int)) ;; boolean? (registry-set! xmlrpc boolean? (make-scalar-type 'boolean (lambda (v) (format "~a" (if (eq? v #t) 1 0))))) ;; string? (registry-set! xmlrpc string? (make-scalar-type 'string)) ;; symbol? - simplifies the conversion into string. (registry-set! xmlrpc symbol? (make-scalar-type 'string)) ;; double? (define (double? x) (and (real? x) (not (integer? x)))) (registry-set! xmlrpc double? (make-scalar-type 'double)) ;; dateTime.iso8601 (registry-set! xmlrpc date? (make-scalar-type 'dateTime.iso8601 date->iso8601)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; conversion functions ;; (-> any/c throw?) (define (unknown v) (error 'any->xmlrpc "unknown type: ~a" v)) ;; (-> any/c xepxr) ;; this is the base function that converts any type registered. will throw error if unknown. (define (any->xmlrpc v) ((registry-ref xmlrpc v unknown) v)) ;; (-> any/c xexpr) ;; this wraps any->xmlrpc with `(value ) xexpr. (define (any->xmlrpc/value v) `(value ,(any->xmlrpc v))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; conversion of composite types. ;; based on the spec there are only two composite types in xmlrpc. ;; * array ;; * struct ;; ;; both list and vector maps well into array. and hash maps to struct. ;; other scheme objects (specifically structs) should first map to hash so it can easily be mapped into struct. ;; this is because structs are non-opaque and requires additional mapping code anyways, so we will have to ;; first convert struct to something else. Also - some struct actually represent scalar values (such as url?). ;; (-> vector? xexpr) (define (array->xmlrpc v) `(array (data ,@(vector->list (vector-map any->xmlrpc/value v))))) ;; register for vector (registry-set! xmlrpc vector? array->xmlrpc) ;; (-> list? xexpr) (define (list->xmlrpc v) `(array (data ,@(map any->xmlrpc/value v)))) ;; register for list (registry-set! xmlrpc list? list->xmlrpc) ;; (-> hash? xexpr) (define (hash->xmlrpc v) `(struct ,@(hash-map v (lambda (k v) `(member (name ,(format "~a" k)) ,(any->xmlrpc/value v)))))) ;; register for hash. (registry-set! xmlrpc hash? hash->xmlrpc) ;; register for error. this might need to be fixed in the future. (define (error->xmlrpc e) (any->xmlrpc (exn-message e))) (registry-set! xmlrpc exn? error->xmlrpc) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; adapter to web handler (define (handle-xmlrpc-result result) ($content-type "text/xml; charset=utf-8") `(methodResponse ,(if (exn? result) `(fault ,(any->xmlrpc/value result)) `(params (param ,(any->xmlrpc/value result)))))) (provide/contract (handle-xmlrpc-result (-> any/c any)) ) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; parser (not a true parser from strings - it parses from sxml) ;; sxpath to retrieve the parameter values (define param-helper (sxpath "/methodCall/params/param/value/node()")) ;; convert the parameter to value. since all types are known in advance this function ;; hardcodes the types instead of using registry for now (can be extended in the future if it makes sense). (define (param->value v) (define (name-helper name) (match name ((list 'name name) name))) (define (member-helper member) (match member ((list 'member (list 'name name) (list 'value value)) (cons name (param->value value))))) (define (value-helper value) (match value ((list 'value v) (param->value v)))) (match v ((list 'int num) (string->number num)) ((list 'double num) (string->number num)) ((list 'string str) str) ((list 'boolean "0") #f) ((list 'boolean "1") #t) ((list 'dateTime.iso8601 date) (read-iso8601 date)) ((list-rest 'struct members) (make-immutable-hash (map member-helper members))) ((list 'array (list-rest 'data values)) (map value-helper values)))) ;; sxml-helper helps to ensure the sxml can be used by sxpath function. (define (sxml-helper xml) (match xml ((list-rest '*TOP* any) xml) (else `(*TOP* ,xml)))) ;; xmlrpc-params returns the actual parameters in scheme values. (define (xmlrpc-params xml) (map param->value (param-helper (sxml-helper xml)))) ;; xmlrpc-method returns the method name. (define (xmlrpc-method xml) (define (helper v) (match v ((list name) name))) (helper ((sxpath "/methodCall/methodName/text()") (sxml-helper xml)))) (provide/contract (xmlrpc-method (-> any/c any)) (xmlrpc-params (-> any/c any)) )