diff --git a/examples/rest/restbucks_CRUD/restbucks.ecf b/examples/rest/restbucks_CRUD/restbucks.ecf index a9c1ab2f..079ba852 100644 --- a/examples/rest/restbucks_CRUD/restbucks.ecf +++ b/examples/rest/restbucks_CRUD/restbucks.ecf @@ -17,6 +17,7 @@ + diff --git a/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e b/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e deleted file mode 100644 index 9e908534..00000000 --- a/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e +++ /dev/null @@ -1,167 +0,0 @@ -note - description: "Summary description for {ORDER_JSON_SERIALIZATION}." - date: "$Date$" - revision: "$Revision$" - -class - ORDER_JSON_SERIALIZATION - -inherit - JSON_SERIALIZER - - JSON_DESERIALIZER - -feature -- Conversion - - to_json (obj: detachable ANY; ctx: JSON_SERIALIZER_CONTEXT): JSON_VALUE - -- JSON value representing the JSON serialization of Eiffel value `obj', in the eventual context `ctx'. - local - j_order: JSON_OBJECT - j_item: JSON_OBJECT - ja: JSON_ARRAY - do - has_error := False - if attached {ORDER} obj as l_order then - create j_order.make_with_capacity (4) - Result := j_order - j_order.put_string (l_order.id, id_key) - if attached l_order.location as loc then - j_order.put_string (loc, location_key) - end - j_order.put_string (l_order.status, status_key) - if attached l_order.items as l_items and then not l_items.is_empty then - create ja.make (l_items.count) - j_order.put (ja, items_key) - across - l_items as ic - loop - if attached {ORDER_ITEM} ic.item as l_item then - create j_item.make_with_capacity (4) - j_item.put_string (l_item.name, name_key) - j_item.put_string (l_item.size, size_key) - j_item.put_integer (l_item.quantity, quantity_key) - j_item.put_string (l_item.option, option_key) - ja.extend (j_item) - end - end - end - else - create {JSON_NULL} Result - has_error := True - end - end - - from_json (a_json: detachable JSON_VALUE; ctx: JSON_DESERIALIZER_CONTEXT; a_type: detachable TYPE [detachable ANY]): detachable ORDER - -- . - local - l_status: detachable STRING_32 - q: NATURAL_8 - is_valid_from_json: BOOLEAN - l_name, l_size, l_option: detachable READABLE_STRING_32 - do - has_error := False - is_valid_from_json := True - if attached {JSON_OBJECT} a_json as jobj then - -- Either new order (i.e no id and no status) - -- or an existing order with `id` and `status` (could be Void, thus use default). - if attached {JSON_STRING} jobj.item (status_key) as j_status then - l_status := j_status.unescaped_string_32 - end - if - attached {JSON_STRING} jobj.item (id_key) as j_id - then - -- Note: the id has to be valid string 8 value! - create Result.make (j_id.unescaped_string_8, l_status) - elseif attached {JSON_NUMBER} jobj.item (id_key) as j_id then - -- Be flexible and accept json number as id. - create Result.make (j_id.integer_64_item.out, l_status) - else - create Result.make_empty - if l_status /= Void then - Result.set_status (l_status) - end - end - if attached {JSON_STRING} jobj.item (location_key) as j_location then - Result.set_location (j_location.unescaped_string_32) - end - if attached {JSON_ARRAY} jobj.item (items_key) as j_items then - across - j_items as ic - loop - if attached {JSON_OBJECT} ic.item as j_item then - if - attached {JSON_NUMBER} j_item.item (quantity_key) as j_quantity and then - j_quantity.integer_64_item < {NATURAL_8}.Max_value - then - q := j_quantity.integer_64_item.to_natural_8 - else - q := 0 - end - if - attached {JSON_STRING} j_item.item (name_key) as j_name and then - attached {JSON_STRING} j_item.item (size_key) as j_size and then - attached {JSON_STRING} j_item.item (option_key) as j_option - then - l_name := j_name.unescaped_string_32 - l_size := j_size.unescaped_string_32 - l_option := j_option.unescaped_string_32 - if is_valid_item_customization (l_name, l_size, l_option, q) then - Result.add_item (create {ORDER_ITEM}.make (l_name, l_size, l_option, q)) - else - is_valid_from_json := False - end - else - is_valid_from_json := False - end - end - end - end - if not is_valid_from_json or Result.items.is_empty then - Result := Void - end - else - is_valid_from_json := a_json = Void or else attached {JSON_NULL} a_json - Result := Void - end - has_error := not is_valid_from_json - end - - has_error: BOOLEAN - -- Error occurred during last `from_json` or `to_json` execution. - -feature {NONE} -- Implementation - - id_key: STRING = "id" - - location_key: STRING = "location" - - status_key: STRING = "status" - - items_key: STRING = "items" - - name_key: STRING = "name" - - size_key: STRING = "size" - - quantity_key: STRING = "quantity" - - option_key: STRING = "option" - -feature -- Validation - - is_valid_item_customization (name: READABLE_STRING_GENERAL; size: READABLE_STRING_GENERAL; option: READABLE_STRING_GENERAL; quantity: NATURAL_8): BOOLEAN - local - ic: ORDER_ITEM_VALIDATION - do - create ic - Result := ic.is_valid_coffee_type (name) and - ic.is_valid_milk_type (option) and - ic.is_valid_size_option (size) and - quantity > 0 - end - -note - copyright: "2011-2017, Javier Velilla and others" - license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" - -end diff --git a/examples/rest/restbucks_CRUD/src/database/restbucks_api.e b/examples/rest/restbucks_CRUD/src/database/restbucks_api.e index 376435ff..9f3fa4d8 100644 --- a/examples/rest/restbucks_CRUD/src/database/restbucks_api.e +++ b/examples/rest/restbucks_CRUD/src/database/restbucks_api.e @@ -13,10 +13,9 @@ feature {NONE} -- Initialization make local - db: BASIC_JSON_FS_DATABASE + db: BASIC_SED_FS_DATABASE do create db.make (database_path) - db.serialization.register (create {ORDER_JSON_SERIALIZATION}, {ORDER}) database := db end diff --git a/examples/rest/restbucks_CRUD/src/resource/order_handler.e b/examples/rest/restbucks_CRUD/src/resource/order_handler.e index e15a8c0c..3c5656f3 100644 --- a/examples/rest/restbucks_CRUD/src/resource/order_handler.e +++ b/examples/rest/restbucks_CRUD/src/resource/order_handler.e @@ -332,24 +332,135 @@ feature {NONE} -- Implementation Repository Layer feature {NONE} -- Conversion - order_to_json (obj: ORDER): JSON_VALUE + order_to_json (obj: ORDER): JSON_OBJECT + local + j_order: JSON_OBJECT + j_item: JSON_OBJECT + ja: JSON_ARRAY do - Result := order_serialization.to_json (obj) - end - - order_from_json (jv: JSON_VALUE): detachable ORDER - do - if attached {ORDER} order_serialization.from_json (jv, {ORDER}) as o then - Result := o + create Result.make_with_capacity (4) + Result.put_string (obj.id, id_key) + if attached obj.location as loc then + Result.put_string (loc, location_key) + end + Result.put_string (obj.status, status_key) + if attached obj.items as l_items and then not l_items.is_empty then + create ja.make (l_items.count) + Result.put (ja, items_key) + across + l_items as ic + loop + if attached {ORDER_ITEM} ic.item as l_item then + create j_item.make_with_capacity (4) + j_item.put_string (l_item.name, name_key) + j_item.put_string (l_item.size, size_key) + j_item.put_integer (l_item.quantity, quantity_key) + j_item.put_string (l_item.option, option_key) + ja.extend (j_item) + end + end end end - order_serialization: JSON_SERIALIZATION + order_from_json (a_json: JSON_VALUE): detachable ORDER + local + l_status: detachable STRING_32 + q: NATURAL_8 + is_valid_from_json: BOOLEAN + l_name, l_size, l_option: detachable READABLE_STRING_32 do - create Result - Result.register (create {ORDER_JSON_SERIALIZATION}, {ORDER}) + is_valid_from_json := True + if attached {JSON_OBJECT} a_json as jobj then + -- Either new order (i.e no id and no status) + -- or an existing order with `id` and `status` (could be Void, thus use default). + if attached {JSON_STRING} jobj.item (status_key) as j_status then + l_status := j_status.unescaped_string_32 + end + if + attached {JSON_STRING} jobj.item (id_key) as j_id + then + -- Note: the id has to be valid string 8 value! + create Result.make (j_id.unescaped_string_8, l_status) + elseif attached {JSON_NUMBER} jobj.item (id_key) as j_id then + -- Be flexible and accept json number as id. + create Result.make (j_id.integer_64_item.out, l_status) + else + create Result.make_empty + if l_status /= Void then + Result.set_status (l_status) + end + end + if attached {JSON_STRING} jobj.item (location_key) as j_location then + Result.set_location (j_location.unescaped_string_32) + end + if attached {JSON_ARRAY} jobj.item (items_key) as j_items then + across + j_items as ic + loop + if attached {JSON_OBJECT} ic.item as j_item then + if + attached {JSON_NUMBER} j_item.item (quantity_key) as j_quantity and then + j_quantity.integer_64_item < {NATURAL_8}.Max_value + then + q := j_quantity.integer_64_item.to_natural_8 + else + q := 0 + end + if + attached {JSON_STRING} j_item.item (name_key) as j_name and then + attached {JSON_STRING} j_item.item (size_key) as j_size and then + attached {JSON_STRING} j_item.item (option_key) as j_option + then + l_name := j_name.unescaped_string_32 + l_size := j_size.unescaped_string_32 + l_option := j_option.unescaped_string_32 + if is_valid_item_customization (l_name, l_size, l_option, q) then + Result.add_item (create {ORDER_ITEM}.make (l_name, l_size, l_option, q)) + else + is_valid_from_json := False + end + else + is_valid_from_json := False + end + end + end + end + if not is_valid_from_json or Result.items.is_empty then + Result := Void + end + else + is_valid_from_json := a_json = Void or else attached {JSON_NULL} a_json + Result := Void + end end + is_valid_item_customization (name: READABLE_STRING_GENERAL; size: READABLE_STRING_GENERAL; option: READABLE_STRING_GENERAL; quantity: NATURAL_8): BOOLEAN + local + ic: ORDER_ITEM_VALIDATION + do + create ic + Result := ic.is_valid_coffee_type (name) and + ic.is_valid_milk_type (option) and + ic.is_valid_size_option (size) and + quantity > 0 + end + + id_key: STRING = "id" + + location_key: STRING = "location" + + status_key: STRING = "status" + + items_key: STRING = "items" + + name_key: STRING = "name" + + size_key: STRING = "size" + + quantity_key: STRING = "quantity" + + option_key: STRING = "option" + note copyright: "2011-2017, Javier Velilla and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/examples/rest/restbucks_CRUD/support/simple_db/simple_db-safe.ecf b/examples/rest/restbucks_CRUD/support/simple_db/simple_db-safe.ecf new file mode 100644 index 00000000..1bee0ca7 --- /dev/null +++ b/examples/rest/restbucks_CRUD/support/simple_db/simple_db-safe.ecf @@ -0,0 +1,19 @@ + + + + + + + + + + + /.*json.*\.e$ + + + + + + + diff --git a/examples/rest/restbucks_CRUD/support/simple_db/simple_db.ecf b/examples/rest/restbucks_CRUD/support/simple_db/simple_db.ecf new file mode 100644 index 00000000..24f0f190 --- /dev/null +++ b/examples/rest/restbucks_CRUD/support/simple_db/simple_db.ecf @@ -0,0 +1,19 @@ + + + + + + + + + + + /.*json.*\.e$ + + + + + + + diff --git a/examples/rest/restbucks_CRUD/src/database/basic_database.e b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_database.e similarity index 100% rename from examples/rest/restbucks_CRUD/src/database/basic_database.e rename to examples/rest/restbucks_CRUD/support/simple_db/src/basic_database.e diff --git a/examples/rest/restbucks_CRUD/src/database/basic_json_fs_database.e b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_json_fs_database.e similarity index 100% rename from examples/rest/restbucks_CRUD/src/database/basic_json_fs_database.e rename to examples/rest/restbucks_CRUD/support/simple_db/src/basic_json_fs_database.e diff --git a/examples/rest/restbucks_CRUD/src/database/basic_memory_database.e b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_memory_database.e similarity index 97% rename from examples/rest/restbucks_CRUD/src/database/basic_memory_database.e rename to examples/rest/restbucks_CRUD/support/simple_db/src/basic_memory_database.e index 1600cbd5..3003b166 100644 --- a/examples/rest/restbucks_CRUD/src/database/basic_memory_database.e +++ b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_memory_database.e @@ -1,6 +1,8 @@ note description: "[ Basic database for simple example based on memory. + + WARNING: for now, not concurrent compliant. ]" date: "$Date$" revision: "$Revision$" diff --git a/examples/rest/restbucks_CRUD/support/simple_db/src/basic_sed_fs_database.e b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_sed_fs_database.e new file mode 100644 index 00000000..31d8bc6d --- /dev/null +++ b/examples/rest/restbucks_CRUD/support/simple_db/src/basic_sed_fs_database.e @@ -0,0 +1,174 @@ +note + description: "[ + Basic database for simple example using SED files. + + (no concurrency access control, ...) + ]" + date: "$Date$" + revision: "$Revision$" + +class + BASIC_SED_FS_DATABASE + +inherit + BASIC_DATABASE + +create + make + +feature {NONE} -- Initialization + + make (a_location: PATH) + local + d: DIRECTORY + do + location := a_location + create serialization + ensure_directory_exists (a_location) + end + +feature -- Access + + location: PATH + + serialization: SED_STORABLE_FACILITIES + +feature -- Access + + count_of (a_entry_type: TYPE [detachable ANY]): INTEGER + local + d: DIRECTORY + do + create d.make_with_path (location.extended (entry_type_name (a_entry_type))) + if d.exists then + across + d.entries as ic + loop + if attached ic.item.extension as e and then e.is_case_insensitive_equal ("sed") then + Result := Result + 1 + end + end + end + end + + has (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): BOOLEAN + -- Has entry of type `a_entry_type` associated with id `a_id`? + local + fut: FILE_UTILITIES + do + Result := fut.file_path_exists (entry_path (a_entry_type, a_id)) + end + + item (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): detachable ANY + local + f: RAW_FILE + s: STRING + l_reader: SED_MEDIUM_READER_WRITER + do + create f.make_with_path (entry_path (a_entry_type, a_id)) + if f.exists then + create s.make (f.count) + f.open_read + create l_reader.make_for_reading (f) + Result := serialization.retrieved (l_reader, True) + f.close + end + end + + save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL]) + local + f: RAW_FILE + l_id: detachable READABLE_STRING_GENERAL + l_writer: SED_MEDIUM_READER_WRITER + do + l_id := cl_entry_id.item + if l_id = Void then + l_id := next_identifier (a_entry_type) + cl_entry_id.replace (l_id) + end + create f.make_with_path (entry_path (a_entry_type, l_id)) + ensure_directory_exists (f.path.parent) + f.open_write + if a_entry /= Void then + create l_writer.make_for_writing (f) + serialization.store (a_entry, l_writer) + end + f.close + end + + delete (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL) + local + f: RAW_FILE + do + create f.make_with_path (entry_path (a_entry_type, a_id)) + if f.exists and then f.is_access_writable then + f.delete + end + end + +feature {NONE} -- Implementation + + ensure_directory_exists (dn: PATH) + local + d: DIRECTORY + do + create d.make_with_path (dn) + if not d.exists then + d.recursive_create_dir + end + end + + entry_path (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): PATH + do + Result := location.extended (entry_type_name (a_entry_type)).extended (a_id).appended_with_extension ("sed") + end + + entry_type_name (a_entry_type: TYPE [detachable ANY]): STRING + do + Result := a_entry_type.name.as_lower + Result.prune_all ('!') + end + + last_id_file_path (a_entry_type: TYPE [detachable ANY]): PATH + do + Result := location.extended (entry_type_name (a_entry_type)).extended ("last-id") + end + + next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8 + local + i: NATURAL_64 + f: RAW_FILE + s: STRING + do + create f.make_with_path (last_id_file_path (a_entry_type)) + ensure_directory_exists (f.path.parent) + if f.exists then + create s.make (f.count) + f.open_read + f.read_line + s := f.last_string + f.close + if s.is_natural_64 then + i := s.to_natural_64 + end + end + from + i := i + 1 + Result := i.out + until + not has (a_entry_type, Result) + loop + i := i + 1 + Result := i.out + end + f.open_write + f.put_string (Result) + f.close + end + +invariant + +note + copyright: "2011-2017, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end