summaryrefslogtreecommitdiffstats
path: root/src/cepis-lookup.cob
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:49:48 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:49:48 -0300
commitf979d07d23098b96e758b3feb555cd06d85d9b2d (patch)
tree47298197a17a3d6c438a1f4cc7cb61afd390cf05 /src/cepis-lookup.cob
downloadcobol-f979d07d23098b96e758b3feb555cd06d85d9b2d.tar.gz
Initial importv0.1.0
Diffstat (limited to 'src/cepis-lookup.cob')
-rw-r--r--src/cepis-lookup.cob324
1 files changed, 324 insertions, 0 deletions
diff --git a/src/cepis-lookup.cob b/src/cepis-lookup.cob
new file mode 100644
index 0000000..cc6e412
--- /dev/null
+++ b/src/cepis-lookup.cob
@@ -0,0 +1,324 @@
+IDENTIFICATION DIVISION.
+PROGRAM-ID. CEPIS-LOOKUP.
+
+*> ---------------------------------------------------------------------
+*> Look up one CEP.
+*>
+*> CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+*> CEPIS-ADDRESS CEPIS-STATUS
+*>
+*> The CEP may be masked or not. It is validated locally first, so a
+*> seven-digit CEP costs no round trip: CEPIS-INVALID-CEP comes back
+*> without a socket ever being opened.
+*>
+*> On CEPIS-OK the address is filled. On anything else CEPIS-ADDRESS is
+*> cleared, so a caller that forgets to test the status reads spaces
+*> rather than the previous CEP's street.
+*>
+*> ADDR-GEO-PRESENT says whether the coordinate exists -- about 57% of
+*> CEPs have one. When it does, read ADDR-GEO-PRECISAO before measuring
+*> any distance with it: "logradouro" means the point is a stretch of
+*> street, with ADDR-GEO-RAIO-M around 100-150 m, while "localidade"
+*> means the CEP is a whole municipality and the point is its centroid,
+*> with a radius of 7 to 40 km. Computing shipping from the second is
+*> wrong by tens of kilometres.
+*> ---------------------------------------------------------------------
+
+DATA DIVISION.
+WORKING-STORAGE SECTION.
+COPY "cepis-http.cpy".
+COPY "cepis-json.cpy".
+
+01 WS-CEP PIC X(8).
+01 WS-GEO-FROM PIC 9(9).
+01 WS-GEO-TO PIC 9(9).
+01 WS-GEO-OK PIC X.
+01 WS-NUM-OK PIC X.
+01 WS-LAT PIC S9(3)V9(6).
+01 WS-LON PIC S9(3)V9(6).
+01 WS-RAIO PIC 9(9).
+01 WS-PRECISAO PIC X(16).
+01 WS-I PIC 9(4).
+01 WS-C PIC X.
+01 WS-DOTS PIC 9(4).
+01 WS-JUNK PIC 9(4).
+01 WS-DIGITS PIC 9(4).
+
+LINKAGE SECTION.
+COPY "cepis.cpy".
+
+PROCEDURE DIVISION USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS.
+
+MAIN-START.
+ PERFORM CLEAR-ADDRESS
+
+ CALL "CEPIS-NORMALIZE-CEP"
+ USING CEPIS-CEP-INPUT WS-CEP CEPIS-STATUS
+ END-CALL
+ IF CEPIS-FAILED
+ GOBACK
+ END-IF
+
+ MOVE SPACES TO HTTPQ-PATH
+ STRING "/" WS-CEP DELIMITED BY SIZE INTO HTTPQ-PATH
+ END-STRING
+ MOVE 9 TO HTTPQ-PATH-LEN
+ MOVE "N" TO HTTPQ-ALLOW-503
+
+ CALL "CEPIS-REQUEST" USING CEPIS-CONFIG CEPIS-HTTP-REQUEST
+ CEPIS-HTTP-RESPONSE CEPIS-STATUS
+ END-CALL
+ IF CEPIS-FAILED
+ GOBACK
+ END-IF
+
+ EVALUATE HTTP-STATUS
+ WHEN 200
+ PERFORM PARSE-ADDRESS
+ WHEN 404
+ SET CEPIS-NOT-FOUND TO TRUE
+ MOVE SPACES TO CEPIS-STATUS-MESSAGE
+ STRING "CEP " WS-CEP " does not exist in the DNE database"
+ DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
+ END-STRING
+ WHEN 400
+ SET CEPIS-INVALID-CEP TO TRUE
+ PERFORM API-MESSAGE
+ WHEN OTHER
+ SET CEPIS-INVALID-RESPONSE TO TRUE
+ MOVE SPACES TO CEPIS-STATUS-MESSAGE
+ STRING "unexpected status " HTTP-STATUS
+ " for CEP " WS-CEP
+ DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
+ END-STRING
+ END-EVALUATE
+ GOBACK.
+
+*> -------------------------------------------------------------------
+CLEAR-ADDRESS.
+ INITIALIZE CEPIS-ADDRESS
+ SET ADDR-NOT-TRUNCATED TO TRUE
+ SET ADDR-GEO-ABSENT TO TRUE
+ MOVE SPACES TO ADDR-GEO-PRECISAO.
+
+*> -------------------------------------------------------------------
+*> The API's own 400 carries a message this client does not have: the
+*> format already passed local validation, so the server knows something
+*> we do not. Surface its "mensagem" when there is one.
+API-MESSAGE.
+ MOVE SPACES TO CEPIS-STATUS-MESSAGE
+ MOVE "mensagem" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-TYPE = "S" AND JS-VALUE-LEN > 0
+ MOVE JS-VALUE TO CEPIS-STATUS-MESSAGE
+ ELSE
+ STRING "CEP " WS-CEP " rejected by the API"
+ DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
+ END-STRING
+ END-IF.
+
+*> -------------------------------------------------------------------
+GET-MEMBER.
+ MOVE 1 TO JS-FROM
+ MOVE HTTP-BODY-LEN TO JS-TO
+ CALL "CEPIS-JSON-GET" USING HTTP-BODY CEPIS-JSON-SCAN
+ END-CALL.
+
+*> -------------------------------------------------------------------
+GET-GEO-MEMBER.
+ MOVE WS-GEO-FROM TO JS-FROM
+ MOVE WS-GEO-TO TO JS-TO
+ CALL "CEPIS-JSON-GET" USING HTTP-BODY CEPIS-JSON-SCAN
+ END-CALL.
+
+*> -------------------------------------------------------------------
+PARSE-ADDRESS.
+ MOVE "cep" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-TYPE = "E"
+ SET CEPIS-INVALID-RESPONSE TO TRUE
+ MOVE "response is not a JSON object -- something between you "
+ & "and the API is answering instead of it"
+ TO CEPIS-STATUS-MESSAGE
+ EXIT PARAGRAPH
+ END-IF
+
+ IF JS-VALUE-LEN > 8
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-CEP
+ IF ADDR-CEP = SPACES
+ MOVE WS-CEP TO ADDR-CEP
+ END-IF
+ STRING ADDR-CEP(1:5) "-" ADDR-CEP(6:3)
+ DELIMITED BY SIZE INTO ADDR-CEP-MASKED
+ END-STRING
+
+ MOVE "logradouro" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 120
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-LOGRADOURO
+
+ MOVE "complemento" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 120
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-COMPLEMENTO
+
+ MOVE "unidade" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 80
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-UNIDADE
+
+ MOVE "bairro" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 100
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-BAIRRO
+
+ MOVE "localidade" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 100
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-LOCALIDADE
+
+ MOVE "uf" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 2
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-UF
+
+ MOVE "estado" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 60
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-ESTADO
+
+ MOVE "regiao" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-VALUE-LEN > 30
+ SET ADDR-WAS-TRUNCATED TO TRUE
+ END-IF
+ MOVE JS-VALUE TO ADDR-REGIAO
+
+ MOVE "ibge" TO JS-KEY
+ PERFORM GET-MEMBER
+ MOVE JS-VALUE TO ADDR-IBGE
+
+ MOVE "gia" TO JS-KEY
+ PERFORM GET-MEMBER
+ MOVE JS-VALUE TO ADDR-GIA
+
+ MOVE "ddd" TO JS-KEY
+ PERFORM GET-MEMBER
+ MOVE JS-VALUE TO ADDR-DDD
+
+ MOVE "siafi" TO JS-KEY
+ PERFORM GET-MEMBER
+ MOVE JS-VALUE TO ADDR-SIAFI
+
+ PERFORM PARSE-GEO
+ SET CEPIS-OK TO TRUE.
+
+*> -------------------------------------------------------------------
+*> The coordinate is omitted entirely when the database has none, so its
+*> absence is normal, not an error. A geo block that is present but
+*> malformed is dropped the same way: the rest of the address is still
+*> useful and losing it over a bad float would be a poor trade.
+PARSE-GEO.
+ SET ADDR-GEO-ABSENT TO TRUE
+ MOVE "geo" TO JS-KEY
+ PERFORM GET-MEMBER
+ IF JS-TYPE NOT = "O"
+ EXIT PARAGRAPH
+ END-IF
+ MOVE JS-VSTART TO WS-GEO-FROM
+ MOVE JS-VEND TO WS-GEO-TO
+ MOVE "Y" TO WS-GEO-OK
+
+ MOVE "lat" TO JS-KEY
+ PERFORM GET-GEO-MEMBER
+ PERFORM CHECK-NUMERIC
+ IF WS-NUM-OK = "Y"
+ COMPUTE WS-LAT = FUNCTION NUMVAL (JS-VALUE)
+ ELSE
+ MOVE "N" TO WS-GEO-OK
+ END-IF
+
+ MOVE "lon" TO JS-KEY
+ PERFORM GET-GEO-MEMBER
+ PERFORM CHECK-NUMERIC
+ IF WS-NUM-OK = "Y"
+ COMPUTE WS-LON = FUNCTION NUMVAL (JS-VALUE)
+ ELSE
+ MOVE "N" TO WS-GEO-OK
+ END-IF
+
+ MOVE "raio_m" TO JS-KEY
+ PERFORM GET-GEO-MEMBER
+ PERFORM CHECK-NUMERIC
+ IF WS-NUM-OK = "Y"
+ COMPUTE WS-RAIO ROUNDED = FUNCTION NUMVAL (JS-VALUE)
+ ELSE
+ MOVE "N" TO WS-GEO-OK
+ END-IF
+
+ MOVE "precisao" TO JS-KEY
+ PERFORM GET-GEO-MEMBER
+ IF JS-TYPE = "S" AND JS-VALUE-LEN > 0
+ MOVE JS-VALUE TO WS-PRECISAO
+ ELSE
+ MOVE "N" TO WS-GEO-OK
+ END-IF
+
+ IF WS-GEO-OK = "Y"
+ SET ADDR-GEO-KNOWN TO TRUE
+ MOVE WS-LAT TO ADDR-GEO-LAT
+ MOVE WS-LON TO ADDR-GEO-LON
+ MOVE WS-RAIO TO ADDR-GEO-RAIO-M
+ MOVE WS-PRECISAO TO ADDR-GEO-PRECISAO
+ END-IF.
+
+*> -------------------------------------------------------------------
+*> FUNCTION NUMVAL answers 0 for text it cannot read, which would turn
+*> "lat": "not a number" into a coordinate off the coast of Africa. The
+*> text is therefore checked before it is converted.
+CHECK-NUMERIC.
+ MOVE "N" TO WS-NUM-OK
+ IF JS-TYPE NOT = "N" AND JS-TYPE NOT = "S"
+ EXIT PARAGRAPH
+ END-IF
+ IF JS-VALUE-LEN = 0 OR JS-VALUE-LEN > 32
+ EXIT PARAGRAPH
+ END-IF
+ MOVE 0 TO WS-DIGITS
+ MOVE 0 TO WS-DOTS
+ MOVE 0 TO WS-JUNK
+ PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > JS-VALUE-LEN
+ MOVE JS-VALUE(WS-I:1) TO WS-C
+ EVALUATE TRUE
+ WHEN WS-C >= "0" AND WS-C <= "9"
+ ADD 1 TO WS-DIGITS
+ WHEN WS-C = "."
+ ADD 1 TO WS-DOTS
+ WHEN (WS-C = "-" OR WS-C = "+") AND WS-I = 1
+ CONTINUE
+ WHEN OTHER
+ ADD 1 TO WS-JUNK
+ END-EVALUATE
+ END-PERFORM
+ IF WS-DIGITS > 0 AND WS-DOTS <= 1 AND WS-JUNK = 0
+ MOVE "Y" TO WS-NUM-OK
+ END-IF.
+
+END PROGRAM CEPIS-LOOKUP.